obsidian-integration-testing 15.0.0 → 17.0.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.cjs +2 -2
- package/dist/lib/cjs/adb.d.cts +10 -0
- package/dist/lib/cjs/avd-name-channel.cjs +74 -0
- package/dist/lib/cjs/avd-name-channel.d.cts +152 -0
- package/dist/lib/cjs/avd-probe-verdict.cjs +11 -4
- package/dist/lib/cjs/avd-probe-verdict.d.cts +7 -0
- package/dist/lib/cjs/device-screenshot.cjs +1 -1
- package/dist/lib/cjs/device-screenshot.d.cts +3 -1
- package/dist/lib/cjs/emulator-backend.cjs +22 -1
- package/dist/lib/cjs/emulator-backend.d.cts +81 -0
- package/dist/lib/cjs/emulator-reclaim.cjs +78 -34
- package/dist/lib/cjs/emulator-reclaim.d.cts +57 -9
- package/dist/lib/cjs/enable-plugin.cjs +17 -8
- package/dist/lib/cjs/enable-plugin.d.cts +3 -1
- package/dist/lib/cjs/host-process-query-verdict.cjs +103 -0
- package/dist/lib/cjs/host-process-query-verdict.d.cts +196 -0
- package/dist/lib/cjs/index.cjs +14 -1
- package/dist/lib/cjs/index.d.cts +3 -1
- package/dist/lib/cjs/label-screenshot.cjs +2 -2
- package/dist/lib/cjs/launched-device-selection.cjs +46 -0
- package/dist/lib/cjs/launched-device-selection.d.cts +112 -0
- package/dist/lib/cjs/library.cjs +1 -1
- package/dist/lib/cjs/paint-out-status-bar.cjs +170 -0
- package/dist/lib/cjs/paint-out-status-bar.d.cts +236 -0
- package/dist/lib/cjs/port-owner-query-verdict.cjs +94 -0
- package/dist/lib/cjs/port-owner-query-verdict.d.cts +161 -0
- package/dist/lib/cjs/resolve-emulator-device-id.cjs +27 -4
- package/dist/lib/cjs/sharp-loader.cjs +1 -1
- package/dist/lib/cjs/sharp-loader.d.cts +67 -2
- package/dist/lib/cjs/soft-keyboard-geometry.cjs +12 -2
- package/dist/lib/cjs/soft-keyboard-geometry.d.cts +17 -2
- package/dist/lib/cjs/soft-keyboard.cjs +130 -28
- package/dist/lib/cjs/soft-keyboard.d.cts +65 -6
- package/dist/lib/cjs/transport-appium.cjs +74 -4
- package/dist/lib/cjs/transport-appium.d.cts +49 -2
- package/dist/lib/cjs/transport-factory.cjs +162 -42
- package/dist/lib/esm/adb.d.mts +10 -0
- package/dist/lib/esm/adb.mjs +2 -2
- package/dist/lib/esm/avd-name-channel.d.mts +152 -0
- package/dist/lib/esm/avd-name-channel.mjs +45 -0
- package/dist/lib/esm/avd-probe-verdict.d.mts +7 -0
- package/dist/lib/esm/avd-probe-verdict.mjs +15 -4
- package/dist/lib/esm/device-screenshot.d.mts +3 -1
- package/dist/lib/esm/device-screenshot.mjs +1 -1
- package/dist/lib/esm/emulator-backend.d.mts +81 -0
- package/dist/lib/esm/emulator-backend.mjs +19 -1
- package/dist/lib/esm/emulator-reclaim.d.mts +57 -9
- package/dist/lib/esm/emulator-reclaim.mjs +84 -36
- package/dist/lib/esm/enable-plugin.d.mts +3 -1
- package/dist/lib/esm/enable-plugin.mjs +17 -8
- package/dist/lib/esm/host-process-query-verdict.d.mts +196 -0
- package/dist/lib/esm/host-process-query-verdict.mjs +74 -0
- package/dist/lib/esm/index.d.mts +3 -1
- package/dist/lib/esm/index.mjs +15 -1
- package/dist/lib/esm/label-screenshot.mjs +2 -2
- package/dist/lib/esm/launched-device-selection.d.mts +112 -0
- package/dist/lib/esm/launched-device-selection.mjs +21 -0
- package/dist/lib/esm/library.mjs +1 -1
- package/dist/lib/esm/paint-out-status-bar.d.mts +236 -0
- package/dist/lib/esm/paint-out-status-bar.mjs +142 -0
- package/dist/lib/esm/port-owner-query-verdict.d.mts +161 -0
- package/dist/lib/esm/port-owner-query-verdict.mjs +73 -0
- package/dist/lib/esm/resolve-emulator-device-id.mjs +32 -4
- package/dist/lib/esm/sharp-loader.d.mts +67 -2
- package/dist/lib/esm/sharp-loader.mjs +1 -1
- package/dist/lib/esm/soft-keyboard-geometry.d.mts +17 -2
- package/dist/lib/esm/soft-keyboard-geometry.mjs +11 -2
- package/dist/lib/esm/soft-keyboard.d.mts +65 -6
- package/dist/lib/esm/soft-keyboard.mjs +131 -28
- package/dist/lib/esm/transport-appium.d.mts +49 -2
- package/dist/lib/esm/transport-appium.mjs +74 -4
- package/dist/lib/esm/transport-factory.mjs +176 -44
- package/package.json +1 -1
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file
|
|
3
|
+
*
|
|
4
|
+
* Pure selection of the device an emulator this run just launched produced.
|
|
5
|
+
*
|
|
6
|
+
* The wait used to take the first online device that was not listed before the
|
|
7
|
+
* launch. The "before" snapshot lists online devices only, so a handset that was
|
|
8
|
+
* `unauthorized`, `offline` or unplugged at that moment — and came online during
|
|
9
|
+
* the boot, which a USB reconnect or accepting the debugging prompt is enough
|
|
10
|
+
* for — looked new, and was adopted as the emulator:
|
|
11
|
+
*
|
|
12
|
+
* ```text
|
|
13
|
+
* AVD "obsidian_test" not found on any existing device, starting a new emulator...
|
|
14
|
+
* Waiting for a new device to appear in ADB ...
|
|
15
|
+
* Device 3C15BN001Y900000 appeared in ADB, waiting for boot to complete...
|
|
16
|
+
* Emulator "obsidian_test" started, device 3C15BN001Y900000 is connected (owned emulator PIDs: [])
|
|
17
|
+
* ```
|
|
18
|
+
*
|
|
19
|
+
* The suite then ran on the phone, and the emulator really launched was owned by
|
|
20
|
+
* nobody. So a new device is the launched emulator only when it is an emulator
|
|
21
|
+
* **and** it says it is serving the launched AVD — which also keeps a second
|
|
22
|
+
* emulator another project started concurrently from being adopted.
|
|
23
|
+
*
|
|
24
|
+
* Kept separate from the integration-only `transport-factory` so the decision is
|
|
25
|
+
* unit-testable; the factory does the `adb` round-trips.
|
|
26
|
+
*/
|
|
27
|
+
/**
|
|
28
|
+
* One new emulator device, with what it answered when asked for its AVD name.
|
|
29
|
+
*/
|
|
30
|
+
export interface LaunchedDeviceCandidate {
|
|
31
|
+
/**
|
|
32
|
+
* The device UDID, e.g. `emulator-5554`.
|
|
33
|
+
*/
|
|
34
|
+
readonly deviceId: string;
|
|
35
|
+
/**
|
|
36
|
+
* The AVD the device reported, or `undefined` when it did not answer.
|
|
37
|
+
*/
|
|
38
|
+
readonly probedAvdName: string | undefined;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* The new devices a poll found, split by whether they can be the launched emulator.
|
|
42
|
+
*/
|
|
43
|
+
export interface NewDeviceSplit {
|
|
44
|
+
/**
|
|
45
|
+
* New emulator devices, to be asked which AVD they serve.
|
|
46
|
+
*/
|
|
47
|
+
readonly emulatorDeviceIds: string[];
|
|
48
|
+
/**
|
|
49
|
+
* New devices that are not emulators (a handset, a TCP-attached device) and are never adopted.
|
|
50
|
+
*/
|
|
51
|
+
readonly nonEmulatorDeviceIds: string[];
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Parameters for {@link resolveLaunchedDevice}.
|
|
55
|
+
*/
|
|
56
|
+
export interface ResolveLaunchedDeviceParams {
|
|
57
|
+
/**
|
|
58
|
+
* The AVD this run launched.
|
|
59
|
+
*/
|
|
60
|
+
readonly avdName: string;
|
|
61
|
+
/**
|
|
62
|
+
* The new emulator devices, with their probe answers.
|
|
63
|
+
*/
|
|
64
|
+
readonly candidates: readonly LaunchedDeviceCandidate[];
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* What the probe answers say about the launched emulator's device.
|
|
68
|
+
*/
|
|
69
|
+
export interface ResolveLaunchedDeviceResult {
|
|
70
|
+
/**
|
|
71
|
+
* The device serving the launched AVD, or `undefined` when none does yet.
|
|
72
|
+
*/
|
|
73
|
+
readonly deviceId: string | undefined;
|
|
74
|
+
/**
|
|
75
|
+
* Devices that answered with a different AVD: another emulator, never this run's.
|
|
76
|
+
*/
|
|
77
|
+
readonly otherAvdDeviceIds: string[];
|
|
78
|
+
/**
|
|
79
|
+
* Devices that did not answer yet, to be asked again on the next poll.
|
|
80
|
+
*/
|
|
81
|
+
readonly unansweredDeviceIds: string[];
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Parameters for {@link splitNewDevices}.
|
|
85
|
+
*/
|
|
86
|
+
export interface SplitNewDevicesParams {
|
|
87
|
+
/**
|
|
88
|
+
* The devices online now.
|
|
89
|
+
*/
|
|
90
|
+
readonly connectedDeviceIds: readonly string[];
|
|
91
|
+
/**
|
|
92
|
+
* The devices online before the launch.
|
|
93
|
+
*/
|
|
94
|
+
readonly deviceIdsBefore: readonly string[];
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Picks the new emulator device that serves the launched AVD.
|
|
98
|
+
*
|
|
99
|
+
* Silence is not a match: a device that did not answer is asked again, never adopted
|
|
100
|
+
* on the strength of being new.
|
|
101
|
+
*
|
|
102
|
+
* @param params - The launched AVD and the probed candidates.
|
|
103
|
+
* @returns The matching device, plus the ones ruled out and the ones still unknown.
|
|
104
|
+
*/
|
|
105
|
+
export declare function resolveLaunchedDevice(params: ResolveLaunchedDeviceParams): ResolveLaunchedDeviceResult;
|
|
106
|
+
/**
|
|
107
|
+
* Splits the devices that were not online before the launch into emulators and everything else.
|
|
108
|
+
*
|
|
109
|
+
* @param params - The devices online now and before the launch.
|
|
110
|
+
* @returns The new devices, split.
|
|
111
|
+
*/
|
|
112
|
+
export declare function splitNewDevices(params: SplitNewDevicesParams): NewDeviceSplit;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { checkIsEmulatorDeviceId } from "./avd-probe-verdict.mjs";
|
|
2
|
+
function resolveLaunchedDevice(params) {
|
|
3
|
+
const match = params.candidates.find((candidate) => candidate.probedAvdName === params.avdName);
|
|
4
|
+
return {
|
|
5
|
+
deviceId: match?.deviceId,
|
|
6
|
+
otherAvdDeviceIds: params.candidates.filter((candidate) => candidate.probedAvdName !== void 0 && candidate.probedAvdName !== params.avdName).map((candidate) => candidate.deviceId),
|
|
7
|
+
unansweredDeviceIds: params.candidates.filter((candidate) => candidate.probedAvdName === void 0).map((candidate) => candidate.deviceId)
|
|
8
|
+
};
|
|
9
|
+
}
|
|
10
|
+
function splitNewDevices(params) {
|
|
11
|
+
const newDeviceIds = params.connectedDeviceIds.filter((deviceId) => !params.deviceIdsBefore.includes(deviceId));
|
|
12
|
+
return {
|
|
13
|
+
emulatorDeviceIds: newDeviceIds.filter((deviceId) => checkIsEmulatorDeviceId(deviceId)),
|
|
14
|
+
nonEmulatorDeviceIds: newDeviceIds.filter((deviceId) => !checkIsEmulatorDeviceId(deviceId))
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
export {
|
|
18
|
+
resolveLaunchedDevice,
|
|
19
|
+
splitNewDevices
|
|
20
|
+
};
|
|
21
|
+
//# sourceMappingURL=data:application/json;base64,ewogICJ2ZXJzaW9uIjogMywKICAic291cmNlcyI6IFsiLi4vLi4vLi4vc3JjL2xhdW5jaGVkLWRldmljZS1zZWxlY3Rpb24udHMiXSwKICAic291cmNlc0NvbnRlbnQiOiBbIi8qKlxuICogQGZpbGVcbiAqXG4gKiBQdXJlIHNlbGVjdGlvbiBvZiB0aGUgZGV2aWNlIGFuIGVtdWxhdG9yIHRoaXMgcnVuIGp1c3QgbGF1bmNoZWQgcHJvZHVjZWQuXG4gKlxuICogVGhlIHdhaXQgdXNlZCB0byB0YWtlIHRoZSBmaXJzdCBvbmxpbmUgZGV2aWNlIHRoYXQgd2FzIG5vdCBsaXN0ZWQgYmVmb3JlIHRoZVxuICogbGF1bmNoLiBUaGUgXCJiZWZvcmVcIiBzbmFwc2hvdCBsaXN0cyBvbmxpbmUgZGV2aWNlcyBvbmx5LCBzbyBhIGhhbmRzZXQgdGhhdCB3YXNcbiAqIGB1bmF1dGhvcml6ZWRgLCBgb2ZmbGluZWAgb3IgdW5wbHVnZ2VkIGF0IHRoYXQgbW9tZW50IFx1MjAxNCBhbmQgY2FtZSBvbmxpbmUgZHVyaW5nXG4gKiB0aGUgYm9vdCwgd2hpY2ggYSBVU0IgcmVjb25uZWN0IG9yIGFjY2VwdGluZyB0aGUgZGVidWdnaW5nIHByb21wdCBpcyBlbm91Z2hcbiAqIGZvciBcdTIwMTQgbG9va2VkIG5ldywgYW5kIHdhcyBhZG9wdGVkIGFzIHRoZSBlbXVsYXRvcjpcbiAqXG4gKiBgYGB0ZXh0XG4gKiBBVkQgXCJvYnNpZGlhbl90ZXN0XCIgbm90IGZvdW5kIG9uIGFueSBleGlzdGluZyBkZXZpY2UsIHN0YXJ0aW5nIGEgbmV3IGVtdWxhdG9yLi4uXG4gKiBXYWl0aW5nIGZvciBhIG5ldyBkZXZpY2UgdG8gYXBwZWFyIGluIEFEQiAuLi5cbiAqIERldmljZSAzQzE1Qk4wMDFZOTAwMDAwIGFwcGVhcmVkIGluIEFEQiwgd2FpdGluZyBmb3IgYm9vdCB0byBjb21wbGV0ZS4uLlxuICogRW11bGF0b3IgXCJvYnNpZGlhbl90ZXN0XCIgc3RhcnRlZCwgZGV2aWNlIDNDMTVCTjAwMVk5MDAwMDAgaXMgY29ubmVjdGVkIChvd25lZCBlbXVsYXRvciBQSURzOiBbXSlcbiAqIGBgYFxuICpcbiAqIFRoZSBzdWl0ZSB0aGVuIHJhbiBvbiB0aGUgcGhvbmUsIGFuZCB0aGUgZW11bGF0b3IgcmVhbGx5IGxhdW5jaGVkIHdhcyBvd25lZCBieVxuICogbm9ib2R5LiBTbyBhIG5ldyBkZXZpY2UgaXMgdGhlIGxhdW5jaGVkIGVtdWxhdG9yIG9ubHkgd2hlbiBpdCBpcyBhbiBlbXVsYXRvclxuICogKiphbmQqKiBpdCBzYXlzIGl0IGlzIHNlcnZpbmcgdGhlIGxhdW5jaGVkIEFWRCBcdTIwMTQgd2hpY2ggYWxzbyBrZWVwcyBhIHNlY29uZFxuICogZW11bGF0b3IgYW5vdGhlciBwcm9qZWN0IHN0YXJ0ZWQgY29uY3VycmVudGx5IGZyb20gYmVpbmcgYWRvcHRlZC5cbiAqXG4gKiBLZXB0IHNlcGFyYXRlIGZyb20gdGhlIGludGVncmF0aW9uLW9ubHkgYHRyYW5zcG9ydC1mYWN0b3J5YCBzbyB0aGUgZGVjaXNpb24gaXNcbiAqIHVuaXQtdGVzdGFibGU7IHRoZSBmYWN0b3J5IGRvZXMgdGhlIGBhZGJgIHJvdW5kLXRyaXBzLlxuICovXG5cbmltcG9ydCB7IGNoZWNrSXNFbXVsYXRvckRldmljZUlkIH0gZnJvbSAnLi9hdmQtcHJvYmUtdmVyZGljdC5tanMnO1xuXG4vKipcbiAqIE9uZSBuZXcgZW11bGF0b3IgZGV2aWNlLCB3aXRoIHdoYXQgaXQgYW5zd2VyZWQgd2hlbiBhc2tlZCBmb3IgaXRzIEFWRCBuYW1lLlxuICovXG5leHBvcnQgaW50ZXJmYWNlIExhdW5jaGVkRGV2aWNlQ2FuZGlkYXRlIHtcbiAgLyoqXG4gICAqIFRoZSBkZXZpY2UgVURJRCwgZS5nLiBgZW11bGF0b3ItNTU1NGAuXG4gICAqL1xuICByZWFkb25seSBkZXZpY2VJZDogc3RyaW5nO1xuXG4gIC8qKlxuICAgKiBUaGUgQVZEIHRoZSBkZXZpY2UgcmVwb3J0ZWQsIG9yIGB1bmRlZmluZWRgIHdoZW4gaXQgZGlkIG5vdCBhbnN3ZXIuXG4gICAqL1xuICByZWFkb25seSBwcm9iZWRBdmROYW1lOiBzdHJpbmcgfCB1bmRlZmluZWQ7XG59XG5cbi8qKlxuICogVGhlIG5ldyBkZXZpY2VzIGEgcG9sbCBmb3VuZCwgc3BsaXQgYnkgd2hldGhlciB0aGV5IGNhbiBiZSB0aGUgbGF1bmNoZWQgZW11bGF0b3IuXG4gKi9cbmV4cG9ydCBpbnRlcmZhY2UgTmV3RGV2aWNlU3BsaXQge1xuICAvKipcbiAgICogTmV3IGVtdWxhdG9yIGRldmljZXMsIHRvIGJlIGFza2VkIHdoaWNoIEFWRCB0aGV5IHNlcnZlLlxuICAgKi9cbiAgcmVhZG9ubHkgZW11bGF0b3JEZXZpY2VJZHM6IHN0cmluZ1tdO1xuXG4gIC8qKlxuICAgKiBOZXcgZGV2aWNlcyB0aGF0IGFyZSBub3QgZW11bGF0b3JzIChhIGhhbmRzZXQsIGEgVENQLWF0dGFjaGVkIGRldmljZSkgYW5kIGFyZSBuZXZlciBhZG9wdGVkLlxuICAgKi9cbiAgcmVhZG9ubHkgbm9uRW11bGF0b3JEZXZpY2VJZHM6IHN0cmluZ1tdO1xufVxuXG4vKipcbiAqIFBhcmFtZXRlcnMgZm9yIHtAbGluayByZXNvbHZlTGF1bmNoZWREZXZpY2V9LlxuICovXG5leHBvcnQgaW50ZXJmYWNlIFJlc29sdmVMYXVuY2hlZERldmljZVBhcmFtcyB7XG4gIC8qKlxuICAgKiBUaGUgQVZEIHRoaXMgcnVuIGxhdW5jaGVkLlxuICAgKi9cbiAgcmVhZG9ubHkgYXZkTmFtZTogc3RyaW5nO1xuXG4gIC8qKlxuICAgKiBUaGUgbmV3IGVtdWxhdG9yIGRldmljZXMsIHdpdGggdGhlaXIgcHJvYmUgYW5zd2Vycy5cbiAgICovXG4gIHJlYWRvbmx5IGNhbmRpZGF0ZXM6IHJlYWRvbmx5IExhdW5jaGVkRGV2aWNlQ2FuZGlkYXRlW107XG59XG5cbi8qKlxuICogV2hhdCB0aGUgcHJvYmUgYW5zd2VycyBzYXkgYWJvdXQgdGhlIGxhdW5jaGVkIGVtdWxhdG9yJ3MgZGV2aWNlLlxuICovXG5leHBvcnQgaW50ZXJmYWNlIFJlc29sdmVMYXVuY2hlZERldmljZVJlc3VsdCB7XG4gIC8qKlxuICAgKiBUaGUgZGV2aWNlIHNlcnZpbmcgdGhlIGxhdW5jaGVkIEFWRCwgb3IgYHVuZGVmaW5lZGAgd2hlbiBub25lIGRvZXMgeWV0LlxuICAgKi9cbiAgcmVhZG9ubHkgZGV2aWNlSWQ6IHN0cmluZyB8IHVuZGVmaW5lZDtcblxuICAvKipcbiAgICogRGV2aWNlcyB0aGF0IGFuc3dlcmVkIHdpdGggYSBkaWZmZXJlbnQgQVZEOiBhbm90aGVyIGVtdWxhdG9yLCBuZXZlciB0aGlzIHJ1bidzLlxuICAgKi9cbiAgcmVhZG9ubHkgb3RoZXJBdmREZXZpY2VJZHM6IHN0cmluZ1tdO1xuXG4gIC8qKlxuICAgKiBEZXZpY2VzIHRoYXQgZGlkIG5vdCBhbnN3ZXIgeWV0LCB0byBiZSBhc2tlZCBhZ2FpbiBvbiB0aGUgbmV4dCBwb2xsLlxuICAgKi9cbiAgcmVhZG9ubHkgdW5hbnN3ZXJlZERldmljZUlkczogc3RyaW5nW107XG59XG5cbi8qKlxuICogUGFyYW1ldGVycyBmb3Ige0BsaW5rIHNwbGl0TmV3RGV2aWNlc30uXG4gKi9cbmV4cG9ydCBpbnRlcmZhY2UgU3BsaXROZXdEZXZpY2VzUGFyYW1zIHtcbiAgLyoqXG4gICAqIFRoZSBkZXZpY2VzIG9ubGluZSBub3cuXG4gICAqL1xuICByZWFkb25seSBjb25uZWN0ZWREZXZpY2VJZHM6IHJlYWRvbmx5IHN0cmluZ1tdO1xuXG4gIC8qKlxuICAgKiBUaGUgZGV2aWNlcyBvbmxpbmUgYmVmb3JlIHRoZSBsYXVuY2guXG4gICAqL1xuICByZWFkb25seSBkZXZpY2VJZHNCZWZvcmU6IHJlYWRvbmx5IHN0cmluZ1tdO1xufVxuXG4vKipcbiAqIFBpY2tzIHRoZSBuZXcgZW11bGF0b3IgZGV2aWNlIHRoYXQgc2VydmVzIHRoZSBsYXVuY2hlZCBBVkQuXG4gKlxuICogU2lsZW5jZSBpcyBub3QgYSBtYXRjaDogYSBkZXZpY2UgdGhhdCBkaWQgbm90IGFuc3dlciBpcyBhc2tlZCBhZ2FpbiwgbmV2ZXIgYWRvcHRlZFxuICogb24gdGhlIHN0cmVuZ3RoIG9mIGJlaW5nIG5ldy5cbiAqXG4gKiBAcGFyYW0gcGFyYW1zIC0gVGhlIGxhdW5jaGVkIEFWRCBhbmQgdGhlIHByb2JlZCBjYW5kaWRhdGVzLlxuICogQHJldHVybnMgVGhlIG1hdGNoaW5nIGRldmljZSwgcGx1cyB0aGUgb25lcyBydWxlZCBvdXQgYW5kIHRoZSBvbmVzIHN0aWxsIHVua25vd24uXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiByZXNvbHZlTGF1bmNoZWREZXZpY2UocGFyYW1zOiBSZXNvbHZlTGF1bmNoZWREZXZpY2VQYXJhbXMpOiBSZXNvbHZlTGF1bmNoZWREZXZpY2VSZXN1bHQge1xuICBjb25zdCBtYXRjaCA9IHBhcmFtcy5jYW5kaWRhdGVzLmZpbmQoKGNhbmRpZGF0ZSkgPT4gY2FuZGlkYXRlLnByb2JlZEF2ZE5hbWUgPT09IHBhcmFtcy5hdmROYW1lKTtcbiAgcmV0dXJuIHtcbiAgICBkZXZpY2VJZDogbWF0Y2g/LmRldmljZUlkLFxuICAgIG90aGVyQXZkRGV2aWNlSWRzOiBwYXJhbXMuY2FuZGlkYXRlc1xuICAgICAgLmZpbHRlcigoY2FuZGlkYXRlKSA9PiBjYW5kaWRhdGUucHJvYmVkQXZkTmFtZSAhPT0gdW5kZWZpbmVkICYmIGNhbmRpZGF0ZS5wcm9iZWRBdmROYW1lICE9PSBwYXJhbXMuYXZkTmFtZSlcbiAgICAgIC5tYXAoKGNhbmRpZGF0ZSkgPT4gY2FuZGlkYXRlLmRldmljZUlkKSxcbiAgICB1bmFuc3dlcmVkRGV2aWNlSWRzOiBwYXJhbXMuY2FuZGlkYXRlcy5maWx0ZXIoKGNhbmRpZGF0ZSkgPT4gY2FuZGlkYXRlLnByb2JlZEF2ZE5hbWUgPT09IHVuZGVmaW5lZCkubWFwKChjYW5kaWRhdGUpID0+IGNhbmRpZGF0ZS5kZXZpY2VJZClcbiAgfTtcbn1cblxuLyoqXG4gKiBTcGxpdHMgdGhlIGRldmljZXMgdGhhdCB3ZXJlIG5vdCBvbmxpbmUgYmVmb3JlIHRoZSBsYXVuY2ggaW50byBlbXVsYXRvcnMgYW5kIGV2ZXJ5dGhpbmcgZWxzZS5cbiAqXG4gKiBAcGFyYW0gcGFyYW1zIC0gVGhlIGRldmljZXMgb25saW5lIG5vdyBhbmQgYmVmb3JlIHRoZSBsYXVuY2guXG4gKiBAcmV0dXJucyBUaGUgbmV3IGRldmljZXMsIHNwbGl0LlxuICovXG5leHBvcnQgZnVuY3Rpb24gc3BsaXROZXdEZXZpY2VzKHBhcmFtczogU3BsaXROZXdEZXZpY2VzUGFyYW1zKTogTmV3RGV2aWNlU3BsaXQge1xuICBjb25zdCBuZXdEZXZpY2VJZHMgPSBwYXJhbXMuY29ubmVjdGVkRGV2aWNlSWRzLmZpbHRlcigoZGV2aWNlSWQpID0+ICFwYXJhbXMuZGV2aWNlSWRzQmVmb3JlLmluY2x1ZGVzKGRldmljZUlkKSk7XG4gIHJldHVybiB7XG4gICAgZW11bGF0b3JEZXZpY2VJZHM6IG5ld0RldmljZUlkcy5maWx0ZXIoKGRldmljZUlkKSA9PiBjaGVja0lzRW11bGF0b3JEZXZpY2VJZChkZXZpY2VJZCkpLFxuICAgIG5vbkVtdWxhdG9yRGV2aWNlSWRzOiBuZXdEZXZpY2VJZHMuZmlsdGVyKChkZXZpY2VJZCkgPT4gIWNoZWNrSXNFbXVsYXRvckRldmljZUlkKGRldmljZUlkKSlcbiAgfTtcbn1cbiJdLAogICJtYXBwaW5ncyI6ICJBQTJCQSxTQUFTLCtCQUErQjtBQTJGakMsU0FBUyxzQkFBc0IsUUFBa0U7QUFDdEcsUUFBTSxRQUFRLE9BQU8sV0FBVyxLQUFLLENBQUMsY0FBYyxVQUFVLGtCQUFrQixPQUFPLE9BQU87QUFDOUYsU0FBTztBQUFBLElBQ0wsVUFBVSxPQUFPO0FBQUEsSUFDakIsbUJBQW1CLE9BQU8sV0FDdkIsT0FBTyxDQUFDLGNBQWMsVUFBVSxrQkFBa0IsVUFBYSxVQUFVLGtCQUFrQixPQUFPLE9BQU8sRUFDekcsSUFBSSxDQUFDLGNBQWMsVUFBVSxRQUFRO0FBQUEsSUFDeEMscUJBQXFCLE9BQU8sV0FBVyxPQUFPLENBQUMsY0FBYyxVQUFVLGtCQUFrQixNQUFTLEVBQUUsSUFBSSxDQUFDLGNBQWMsVUFBVSxRQUFRO0FBQUEsRUFDM0k7QUFDRjtBQVFPLFNBQVMsZ0JBQWdCLFFBQStDO0FBQzdFLFFBQU0sZUFBZSxPQUFPLG1CQUFtQixPQUFPLENBQUMsYUFBYSxDQUFDLE9BQU8sZ0JBQWdCLFNBQVMsUUFBUSxDQUFDO0FBQzlHLFNBQU87QUFBQSxJQUNMLG1CQUFtQixhQUFhLE9BQU8sQ0FBQyxhQUFhLHdCQUF3QixRQUFRLENBQUM7QUFBQSxJQUN0RixzQkFBc0IsYUFBYSxPQUFPLENBQUMsYUFBYSxDQUFDLHdCQUF3QixRQUFRLENBQUM7QUFBQSxFQUM1RjtBQUNGOyIsCiAgIm5hbWVzIjogW10KfQo=
|
package/dist/lib/esm/library.mjs
CHANGED
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file
|
|
3
|
+
*
|
|
4
|
+
* Paints the device's status bar out of a captured frame, so a device capture
|
|
5
|
+
* can be committed.
|
|
6
|
+
*
|
|
7
|
+
* A framebuffer carries the status bar, and a status bar carries a wall clock, a
|
|
8
|
+
* battery that charges while the emulator runs and a radio that comes and goes.
|
|
9
|
+
* So re-running a capture suite on an unchanged tree rewrites its PNGs with
|
|
10
|
+
* content nobody can commit, and the churn is invisible until someone diffs two
|
|
11
|
+
* runs. `device-screenshot`'s header has always said a device capture is not
|
|
12
|
+
* byte-reproducible; this is what makes one.
|
|
13
|
+
*
|
|
14
|
+
* **Pinning the bar was tried first, and is NOT sufficient — that finding is the
|
|
15
|
+
* reason this helper exists rather than a `withSystemUiDemoMode` one.** SystemUI
|
|
16
|
+
* demo mode (`sysui_demo_allowed`, plus one `com.android.systemui.demo`
|
|
17
|
+
* broadcast per area: clock at a fixed `hhmm`, battery full and unplugged,
|
|
18
|
+
* radios hidden, notification icons off) held the clock at `12:00` and two runs
|
|
19
|
+
* still disagreed on **1823 full-contrast pixels**, because the bar's leading
|
|
20
|
+
* group is laid out at a different offset between emulator boots —
|
|
21
|
+
* intermittently, roughly one boot in four. The Tuner's `icon_blacklist` does
|
|
22
|
+
* not help either: demo mode draws its own icon layer and ignores it. Measured
|
|
23
|
+
* on a real AVD across five paired capture runs for `obsidian-link-picker`'s
|
|
24
|
+
* store frames, which is where this recipe was worked out before it moved here.
|
|
25
|
+
*
|
|
26
|
+
* Removing the band rather than pinning it also retires every device setting
|
|
27
|
+
* that pinning needed, and nothing in a status bar is evidence about a plugin. A
|
|
28
|
+
* listing frame without one is what a listing frame normally looks like.
|
|
29
|
+
*
|
|
30
|
+
* **The band's height is the caller's to measure, and there is deliberately no
|
|
31
|
+
* default.** A wrong height does not fail loudly by itself — it paints over the
|
|
32
|
+
* product. What stands between the two is {@link resolveStatusBarBand}: the row
|
|
33
|
+
* just under the bar's own content must be one flat color all the way across,
|
|
34
|
+
* which is what an empty band looks like and is also where the fill color is
|
|
35
|
+
* sampled from. A taller bar, or an Obsidian that draws higher, fails the
|
|
36
|
+
* capture instead.
|
|
37
|
+
*
|
|
38
|
+
* Compositing over a captured frame is not a new liberty: `labelScreenshot`
|
|
39
|
+
* already draws the caption band across the bottom of the same image.
|
|
40
|
+
*/
|
|
41
|
+
/**
|
|
42
|
+
* Parameters for {@link buildStatusBarBandFailureMessage}.
|
|
43
|
+
*/
|
|
44
|
+
export interface BuildStatusBarBandFailureMessageParams {
|
|
45
|
+
/**
|
|
46
|
+
* How far in from each edge the row was scanned, in pixels.
|
|
47
|
+
*/
|
|
48
|
+
readonly edgeMarginInPixels: number;
|
|
49
|
+
/**
|
|
50
|
+
* The height of the band that was about to be painted, in pixels.
|
|
51
|
+
*/
|
|
52
|
+
readonly heightInPixels: number;
|
|
53
|
+
/**
|
|
54
|
+
* The verdict being reported.
|
|
55
|
+
*/
|
|
56
|
+
readonly verdict: StatusBarBandVerdict;
|
|
57
|
+
/**
|
|
58
|
+
* The frame's width, in pixels.
|
|
59
|
+
*/
|
|
60
|
+
readonly widthInPixels: number;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Options for {@link paintOutStatusBar}.
|
|
64
|
+
*/
|
|
65
|
+
export interface PaintOutStatusBarOptions {
|
|
66
|
+
/**
|
|
67
|
+
* How far in from each edge the clear-of-chrome check reads.
|
|
68
|
+
*
|
|
69
|
+
* @default {@link DEFAULT_STATUS_BAR_EDGE_MARGIN_IN_PIXELS}
|
|
70
|
+
*/
|
|
71
|
+
readonly edgeMarginInPixels?: number;
|
|
72
|
+
/**
|
|
73
|
+
* How tall the device's status bar is, in framebuffer pixels.
|
|
74
|
+
*
|
|
75
|
+
* **Measured, never assumed, and deliberately without a default.** 24dp at a
|
|
76
|
+
* density of 320 is 48px, and that is one AVD's number rather than a constant
|
|
77
|
+
* of Android. Measure it on a frame: the bar's own content occupies the top
|
|
78
|
+
* rows, a few empty rows follow, and the app's top chrome starts at exactly
|
|
79
|
+
* this offset.
|
|
80
|
+
*/
|
|
81
|
+
readonly heightInPixels: number;
|
|
82
|
+
/**
|
|
83
|
+
* How far above the band's bottom edge the fill color is sampled, in pixels.
|
|
84
|
+
*
|
|
85
|
+
* @default {@link DEFAULT_STATUS_BAR_SAMPLE_INSET_IN_PIXELS}
|
|
86
|
+
*/
|
|
87
|
+
readonly sampleInsetInPixels?: number;
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Parameters for {@link resolveStatusBarBand}.
|
|
91
|
+
*/
|
|
92
|
+
export interface ResolveStatusBarBandParams {
|
|
93
|
+
/**
|
|
94
|
+
* How many bytes each pixel occupies in {@link ResolveStatusBarBandParams.pixels} — 3 for RGB, 4 for RGBA.
|
|
95
|
+
*/
|
|
96
|
+
readonly channelCount: number;
|
|
97
|
+
/**
|
|
98
|
+
* How far in from each edge to start reading, in pixels.
|
|
99
|
+
*
|
|
100
|
+
* A device's rounded corners darken a handful of pixels at both ends of every
|
|
101
|
+
* row in the band, constantly and harmlessly; the check is about what the APP
|
|
102
|
+
* draws, so it ignores them.
|
|
103
|
+
*
|
|
104
|
+
* @default {@link DEFAULT_STATUS_BAR_EDGE_MARGIN_IN_PIXELS}
|
|
105
|
+
*/
|
|
106
|
+
readonly edgeMarginInPixels?: number;
|
|
107
|
+
/**
|
|
108
|
+
* The height of the band, in pixels — the same number {@link paintOutStatusBar} would paint.
|
|
109
|
+
*/
|
|
110
|
+
readonly heightInPixels: number;
|
|
111
|
+
/**
|
|
112
|
+
* The frame's raw pixels, row-major, as `sharp`'s `raw()` reports them.
|
|
113
|
+
*/
|
|
114
|
+
readonly pixels: Uint8Array;
|
|
115
|
+
/**
|
|
116
|
+
* How far above the band's bottom edge the row is read from, in pixels.
|
|
117
|
+
*
|
|
118
|
+
* The bar's own content stops short of the band's bottom edge, so the rows
|
|
119
|
+
* immediately under it are empty; this picks one of them.
|
|
120
|
+
*
|
|
121
|
+
* @default {@link DEFAULT_STATUS_BAR_SAMPLE_INSET_IN_PIXELS}
|
|
122
|
+
*/
|
|
123
|
+
readonly sampleInsetInPixels?: number;
|
|
124
|
+
/**
|
|
125
|
+
* The frame's width, in pixels.
|
|
126
|
+
*/
|
|
127
|
+
readonly widthInPixels: number;
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* A color read out of a frame, channel by channel.
|
|
131
|
+
*
|
|
132
|
+
* This package's own type rather than `sharp`'s, because it is part of the
|
|
133
|
+
* public surface and `sharp` is an OPTIONAL peer: a consumer that never installs
|
|
134
|
+
* it still names this. Structurally what `sharp`'s `background` takes, which is
|
|
135
|
+
* how the fill block is created from one with no conversion in between.
|
|
136
|
+
*/
|
|
137
|
+
export interface StatusBarBandColor {
|
|
138
|
+
/**
|
|
139
|
+
* The blue channel, 0-255.
|
|
140
|
+
*/
|
|
141
|
+
readonly b: number;
|
|
142
|
+
/**
|
|
143
|
+
* The green channel, 0-255.
|
|
144
|
+
*/
|
|
145
|
+
readonly g: number;
|
|
146
|
+
/**
|
|
147
|
+
* The red channel, 0-255.
|
|
148
|
+
*/
|
|
149
|
+
readonly r: number;
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* What the row under the status bar's content turned out to hold.
|
|
153
|
+
*
|
|
154
|
+
* Verdict-as-data, like the compatibility and teardown checks: the reading and
|
|
155
|
+
* the throwing are separate, so a caller can ask what a frame looks like without
|
|
156
|
+
* having to catch an error to find out.
|
|
157
|
+
*/
|
|
158
|
+
export interface StatusBarBandVerdict {
|
|
159
|
+
/**
|
|
160
|
+
* The color the band would be filled with — read from the first scanned column of the sample row.
|
|
161
|
+
*/
|
|
162
|
+
readonly backgroundColor: StatusBarBandColor;
|
|
163
|
+
/**
|
|
164
|
+
* How many distinct colors the scanned row holds. One is what an empty band looks like.
|
|
165
|
+
*/
|
|
166
|
+
readonly distinctColorCount: number;
|
|
167
|
+
/**
|
|
168
|
+
* The color at {@link StatusBarBandVerdict.firstDifferentColumnXInPixels}, or `null` when the row is flat.
|
|
169
|
+
*/
|
|
170
|
+
readonly firstDifferentColor: null | StatusBarBandColor;
|
|
171
|
+
/**
|
|
172
|
+
* The first scanned column that differs from {@link StatusBarBandVerdict.backgroundColor}, or `null` when the row is flat.
|
|
173
|
+
*/
|
|
174
|
+
readonly firstDifferentColumnXInPixels: null | number;
|
|
175
|
+
/**
|
|
176
|
+
* Whether the row is one flat color, and so whether the band holds nothing but device chrome.
|
|
177
|
+
*/
|
|
178
|
+
readonly isClear: boolean;
|
|
179
|
+
/**
|
|
180
|
+
* Which row was read, as a distance from the top of the frame in pixels.
|
|
181
|
+
*/
|
|
182
|
+
readonly sampleRowYInPixels: number;
|
|
183
|
+
}
|
|
184
|
+
/**
|
|
185
|
+
* How far in from each edge the clear-of-chrome check reads, in pixels.
|
|
186
|
+
*
|
|
187
|
+
* Exported because a caller tightening or loosening it wants to say so relative
|
|
188
|
+
* to this rather than in the abstract.
|
|
189
|
+
*/
|
|
190
|
+
export declare const DEFAULT_STATUS_BAR_EDGE_MARGIN_IN_PIXELS = 100;
|
|
191
|
+
/**
|
|
192
|
+
* How far above the band's bottom edge the sample row sits, in pixels.
|
|
193
|
+
*
|
|
194
|
+
* Four rows up: far enough to clear the bar's own content, close enough to still
|
|
195
|
+
* be inside the band on every device this has been measured on.
|
|
196
|
+
*/
|
|
197
|
+
export declare const DEFAULT_STATUS_BAR_SAMPLE_INSET_IN_PIXELS = 4;
|
|
198
|
+
/**
|
|
199
|
+
* Builds the message a band that is not clear is refused with.
|
|
200
|
+
*
|
|
201
|
+
* Separate from the throwing so it can be asserted on directly: what makes this
|
|
202
|
+
* failure actionable is the measurement, not the wording around it.
|
|
203
|
+
*
|
|
204
|
+
* @param params - The verdict and the geometry it was read with.
|
|
205
|
+
* @returns The message, ready to throw.
|
|
206
|
+
*/
|
|
207
|
+
export declare function buildStatusBarBandFailureMessage(params: BuildStatusBarBandFailureMessageParams): string;
|
|
208
|
+
/**
|
|
209
|
+
* Fills the status-bar band with the background behind it, so the frame carries no device chrome.
|
|
210
|
+
*
|
|
211
|
+
* The image keeps its dimensions exactly: the band is composited OVER the frame,
|
|
212
|
+
* never cropped off it, because a store listing expects a specific size and a
|
|
213
|
+
* cropped frame is a different picture.
|
|
214
|
+
*
|
|
215
|
+
* @param bytes - The device framebuffer, as `captureDeviceScreenshot` returned it.
|
|
216
|
+
* @param options - How tall the band is, and how the check reads it.
|
|
217
|
+
* @returns A {@link Promise} that resolves to the same frame with the band painted out, as PNG bytes.
|
|
218
|
+
* @throws Error if `sharp` is not installed, the band is as tall as the frame or taller, or the row under
|
|
219
|
+
* the bar's content is not one flat color — see {@link buildStatusBarBandFailureMessage}.
|
|
220
|
+
*/
|
|
221
|
+
export declare function paintOutStatusBar(bytes: Uint8Array, options: PaintOutStatusBarOptions): Promise<Uint8Array>;
|
|
222
|
+
/**
|
|
223
|
+
* Reads the row under the status bar's content and says whether the band is clear of the app.
|
|
224
|
+
*
|
|
225
|
+
* The band's height is a number a caller measured once, and a measured number
|
|
226
|
+
* can go stale — a taller status bar, or an Obsidian that draws higher, would
|
|
227
|
+
* have the capture quietly paint over the product. So the row just below the
|
|
228
|
+
* bar's own content is required to be one flat color all the way across: that
|
|
229
|
+
* is what an empty band looks like, and it is what the fill color is read from.
|
|
230
|
+
*
|
|
231
|
+
* @param params - The frame's raw pixels and the band's geometry.
|
|
232
|
+
* @returns What the scanned row holds.
|
|
233
|
+
* @throws Error if the geometry is degenerate — a non-positive dimension, a sample row above the top of
|
|
234
|
+
* the frame, a margin that leaves nothing to scan, or a buffer too short to hold the row.
|
|
235
|
+
*/
|
|
236
|
+
export declare function resolveStatusBarBand(params: ResolveStatusBarBandParams): StatusBarBandVerdict;
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import { importSharp } from "./sharp-loader.mjs";
|
|
2
|
+
const DEFAULT_STATUS_BAR_EDGE_MARGIN_IN_PIXELS = 100;
|
|
3
|
+
const DEFAULT_STATUS_BAR_SAMPLE_INSET_IN_PIXELS = 4;
|
|
4
|
+
const BLUE_CHANNEL_OFFSET = 2;
|
|
5
|
+
const FILL_CHANNEL_COUNT = 3;
|
|
6
|
+
const GREEN_CHANNEL_OFFSET = 1;
|
|
7
|
+
const MINIMUM_SCANNED_COLUMN_COUNT = 2;
|
|
8
|
+
const RED_CHANNEL_OFFSET = 0;
|
|
9
|
+
function buildStatusBarBandFailureMessage(params) {
|
|
10
|
+
const {
|
|
11
|
+
edgeMarginInPixels,
|
|
12
|
+
heightInPixels,
|
|
13
|
+
verdict,
|
|
14
|
+
widthInPixels
|
|
15
|
+
} = params;
|
|
16
|
+
const lines = [
|
|
17
|
+
`paintOutStatusBar: the row under the status bar holds more than one color, so a band of ${String(heightInPixels)}px would paint over the app rather than over the device.`,
|
|
18
|
+
`band=${String(heightInPixels)}px sampleRow=y${String(verdict.sampleRowYInPixels)} scanned x${String(edgeMarginInPixels)}..${String(widthInPixels - edgeMarginInPixels)} of ${String(widthInPixels)}px distinctColors=${String(verdict.distinctColorCount)}`,
|
|
19
|
+
`sampled ${formatColor(verdict.backgroundColor)}, but x=${String(verdict.firstDifferentColumnXInPixels)} is ${formatColor(verdict.firstDifferentColor)}`,
|
|
20
|
+
"A taller status bar, or an Obsidian that draws higher, both look like this. Measure the band on a fresh frame and pass the height you measured: enlarging it until the check passes is how a capture paints over the product."
|
|
21
|
+
];
|
|
22
|
+
return lines.join("\n");
|
|
23
|
+
}
|
|
24
|
+
async function paintOutStatusBar(bytes, options) {
|
|
25
|
+
const { heightInPixels } = options;
|
|
26
|
+
const edgeMarginInPixels = options.edgeMarginInPixels ?? DEFAULT_STATUS_BAR_EDGE_MARGIN_IN_PIXELS;
|
|
27
|
+
const sharp = await importSharp("paintOutStatusBar");
|
|
28
|
+
const { data, info } = await sharp(bytes).ensureAlpha().raw().toBuffer({ resolveWithObject: true });
|
|
29
|
+
if (heightInPixels >= info.height) {
|
|
30
|
+
throw new Error(
|
|
31
|
+
`paintOutStatusBar: a band of ${String(heightInPixels)}px would cover a frame that is only ${String(info.height)}px tall. That is a measurement of some other device, not of this frame.`
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
const verdict = resolveStatusBarBand({
|
|
35
|
+
channelCount: info.channels,
|
|
36
|
+
edgeMarginInPixels,
|
|
37
|
+
heightInPixels,
|
|
38
|
+
pixels: data,
|
|
39
|
+
...options.sampleInsetInPixels !== void 0 && { sampleInsetInPixels: options.sampleInsetInPixels },
|
|
40
|
+
widthInPixels: info.width
|
|
41
|
+
});
|
|
42
|
+
if (!verdict.isClear) {
|
|
43
|
+
throw new Error(buildStatusBarBandFailureMessage({
|
|
44
|
+
edgeMarginInPixels,
|
|
45
|
+
heightInPixels,
|
|
46
|
+
verdict,
|
|
47
|
+
widthInPixels: info.width
|
|
48
|
+
}));
|
|
49
|
+
}
|
|
50
|
+
const layer = {
|
|
51
|
+
input: {
|
|
52
|
+
create: {
|
|
53
|
+
background: verdict.backgroundColor,
|
|
54
|
+
channels: FILL_CHANNEL_COUNT,
|
|
55
|
+
height: heightInPixels,
|
|
56
|
+
width: info.width
|
|
57
|
+
}
|
|
58
|
+
},
|
|
59
|
+
left: 0,
|
|
60
|
+
top: 0
|
|
61
|
+
};
|
|
62
|
+
const painted = await sharp(bytes).composite([layer]).png().toBuffer();
|
|
63
|
+
return new Uint8Array(painted);
|
|
64
|
+
}
|
|
65
|
+
function resolveStatusBarBand(params) {
|
|
66
|
+
const {
|
|
67
|
+
channelCount,
|
|
68
|
+
heightInPixels,
|
|
69
|
+
pixels,
|
|
70
|
+
widthInPixels
|
|
71
|
+
} = params;
|
|
72
|
+
const edgeMarginInPixels = params.edgeMarginInPixels ?? DEFAULT_STATUS_BAR_EDGE_MARGIN_IN_PIXELS;
|
|
73
|
+
const sampleInsetInPixels = params.sampleInsetInPixels ?? DEFAULT_STATUS_BAR_SAMPLE_INSET_IN_PIXELS;
|
|
74
|
+
assertPositive("widthInPixels", widthInPixels);
|
|
75
|
+
assertPositive("heightInPixels", heightInPixels);
|
|
76
|
+
assertPositive("channelCount", channelCount);
|
|
77
|
+
const sampleRowYInPixels = heightInPixels - sampleInsetInPixels;
|
|
78
|
+
if (sampleRowYInPixels < 0) {
|
|
79
|
+
throw new Error(
|
|
80
|
+
`resolveStatusBarBand: a sample inset of ${String(sampleInsetInPixels)}px puts the sample row above the top of the frame for a band of ${String(heightInPixels)}px.`
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
const lastScannedColumnXInPixels = widthInPixels - edgeMarginInPixels;
|
|
84
|
+
if (lastScannedColumnXInPixels - edgeMarginInPixels < MINIMUM_SCANNED_COLUMN_COUNT) {
|
|
85
|
+
throw new Error(
|
|
86
|
+
`resolveStatusBarBand: an edge margin of ${String(edgeMarginInPixels)}px leaves fewer than ${String(MINIMUM_SCANNED_COLUMN_COUNT)} columns of a ${String(widthInPixels)}px frame to scan, which is a check that cannot fail.`
|
|
87
|
+
);
|
|
88
|
+
}
|
|
89
|
+
const rowStartOffset = sampleRowYInPixels * widthInPixels * channelCount;
|
|
90
|
+
const requiredLength = rowStartOffset + widthInPixels * channelCount;
|
|
91
|
+
if (pixels.length < requiredLength) {
|
|
92
|
+
throw new Error(
|
|
93
|
+
`resolveStatusBarBand: the frame holds ${String(pixels.length)} bytes, too few for row y${String(sampleRowYInPixels)} of a ${String(widthInPixels)}px-wide frame at ${String(channelCount)} channels, which needs ${String(requiredLength)}.`
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
const backgroundColor = readColor(pixels, rowStartOffset + edgeMarginInPixels * channelCount);
|
|
97
|
+
const backgroundKey = formatColor(backgroundColor);
|
|
98
|
+
const colorKeys = /* @__PURE__ */ new Set();
|
|
99
|
+
let firstDifferentColor = null;
|
|
100
|
+
let firstDifferentColumnXInPixels = null;
|
|
101
|
+
for (let x = edgeMarginInPixels; x < lastScannedColumnXInPixels; x++) {
|
|
102
|
+
const color = readColor(pixels, rowStartOffset + x * channelCount);
|
|
103
|
+
const colorKey = formatColor(color);
|
|
104
|
+
colorKeys.add(colorKey);
|
|
105
|
+
if (firstDifferentColumnXInPixels !== null || colorKey === backgroundKey) {
|
|
106
|
+
continue;
|
|
107
|
+
}
|
|
108
|
+
firstDifferentColor = color;
|
|
109
|
+
firstDifferentColumnXInPixels = x;
|
|
110
|
+
}
|
|
111
|
+
return {
|
|
112
|
+
backgroundColor,
|
|
113
|
+
distinctColorCount: colorKeys.size,
|
|
114
|
+
firstDifferentColor,
|
|
115
|
+
firstDifferentColumnXInPixels,
|
|
116
|
+
isClear: colorKeys.size === 1,
|
|
117
|
+
sampleRowYInPixels
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
function assertPositive(name, value) {
|
|
121
|
+
if (!Number.isFinite(value) || value <= 0) {
|
|
122
|
+
throw new Error(`resolveStatusBarBand: ${name} must be a positive number, got ${String(value)}.`);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
function formatColor(color) {
|
|
126
|
+
return color === null ? "(none)" : `rgb(${String(color.r)},${String(color.g)},${String(color.b)})`;
|
|
127
|
+
}
|
|
128
|
+
function readColor(pixels, offset) {
|
|
129
|
+
return {
|
|
130
|
+
b: pixels[offset + BLUE_CHANNEL_OFFSET] ?? 0,
|
|
131
|
+
g: pixels[offset + GREEN_CHANNEL_OFFSET] ?? 0,
|
|
132
|
+
r: pixels[offset + RED_CHANNEL_OFFSET] ?? 0
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
export {
|
|
136
|
+
DEFAULT_STATUS_BAR_EDGE_MARGIN_IN_PIXELS,
|
|
137
|
+
DEFAULT_STATUS_BAR_SAMPLE_INSET_IN_PIXELS,
|
|
138
|
+
buildStatusBarBandFailureMessage,
|
|
139
|
+
paintOutStatusBar,
|
|
140
|
+
resolveStatusBarBand
|
|
141
|
+
};
|
|
142
|
+
//# sourceMappingURL=data:application/json;base64,ewogICJ2ZXJzaW9uIjogMywKICAic291cmNlcyI6IFsiLi4vLi4vLi4vc3JjL3BhaW50LW91dC1zdGF0dXMtYmFyLnRzIl0sCiAgInNvdXJjZXNDb250ZW50IjogWyIvKipcbiAqIEBmaWxlXG4gKlxuICogUGFpbnRzIHRoZSBkZXZpY2UncyBzdGF0dXMgYmFyIG91dCBvZiBhIGNhcHR1cmVkIGZyYW1lLCBzbyBhIGRldmljZSBjYXB0dXJlXG4gKiBjYW4gYmUgY29tbWl0dGVkLlxuICpcbiAqIEEgZnJhbWVidWZmZXIgY2FycmllcyB0aGUgc3RhdHVzIGJhciwgYW5kIGEgc3RhdHVzIGJhciBjYXJyaWVzIGEgd2FsbCBjbG9jaywgYVxuICogYmF0dGVyeSB0aGF0IGNoYXJnZXMgd2hpbGUgdGhlIGVtdWxhdG9yIHJ1bnMgYW5kIGEgcmFkaW8gdGhhdCBjb21lcyBhbmQgZ29lcy5cbiAqIFNvIHJlLXJ1bm5pbmcgYSBjYXB0dXJlIHN1aXRlIG9uIGFuIHVuY2hhbmdlZCB0cmVlIHJld3JpdGVzIGl0cyBQTkdzIHdpdGhcbiAqIGNvbnRlbnQgbm9ib2R5IGNhbiBjb21taXQsIGFuZCB0aGUgY2h1cm4gaXMgaW52aXNpYmxlIHVudGlsIHNvbWVvbmUgZGlmZnMgdHdvXG4gKiBydW5zLiBgZGV2aWNlLXNjcmVlbnNob3RgJ3MgaGVhZGVyIGhhcyBhbHdheXMgc2FpZCBhIGRldmljZSBjYXB0dXJlIGlzIG5vdFxuICogYnl0ZS1yZXByb2R1Y2libGU7IHRoaXMgaXMgd2hhdCBtYWtlcyBvbmUuXG4gKlxuICogKipQaW5uaW5nIHRoZSBiYXIgd2FzIHRyaWVkIGZpcnN0LCBhbmQgaXMgTk9UIHN1ZmZpY2llbnQgXHUyMDE0IHRoYXQgZmluZGluZyBpcyB0aGVcbiAqIHJlYXNvbiB0aGlzIGhlbHBlciBleGlzdHMgcmF0aGVyIHRoYW4gYSBgd2l0aFN5c3RlbVVpRGVtb01vZGVgIG9uZS4qKiBTeXN0ZW1VSVxuICogZGVtbyBtb2RlIChgc3lzdWlfZGVtb19hbGxvd2VkYCwgcGx1cyBvbmUgYGNvbS5hbmRyb2lkLnN5c3RlbXVpLmRlbW9gXG4gKiBicm9hZGNhc3QgcGVyIGFyZWE6IGNsb2NrIGF0IGEgZml4ZWQgYGhobW1gLCBiYXR0ZXJ5IGZ1bGwgYW5kIHVucGx1Z2dlZCxcbiAqIHJhZGlvcyBoaWRkZW4sIG5vdGlmaWNhdGlvbiBpY29ucyBvZmYpIGhlbGQgdGhlIGNsb2NrIGF0IGAxMjowMGAgYW5kIHR3byBydW5zXG4gKiBzdGlsbCBkaXNhZ3JlZWQgb24gKioxODIzIGZ1bGwtY29udHJhc3QgcGl4ZWxzKiosIGJlY2F1c2UgdGhlIGJhcidzIGxlYWRpbmdcbiAqIGdyb3VwIGlzIGxhaWQgb3V0IGF0IGEgZGlmZmVyZW50IG9mZnNldCBiZXR3ZWVuIGVtdWxhdG9yIGJvb3RzIFx1MjAxNFxuICogaW50ZXJtaXR0ZW50bHksIHJvdWdobHkgb25lIGJvb3QgaW4gZm91ci4gVGhlIFR1bmVyJ3MgYGljb25fYmxhY2tsaXN0YCBkb2VzXG4gKiBub3QgaGVscCBlaXRoZXI6IGRlbW8gbW9kZSBkcmF3cyBpdHMgb3duIGljb24gbGF5ZXIgYW5kIGlnbm9yZXMgaXQuIE1lYXN1cmVkXG4gKiBvbiBhIHJlYWwgQVZEIGFjcm9zcyBmaXZlIHBhaXJlZCBjYXB0dXJlIHJ1bnMgZm9yIGBvYnNpZGlhbi1saW5rLXBpY2tlcmAnc1xuICogc3RvcmUgZnJhbWVzLCB3aGljaCBpcyB3aGVyZSB0aGlzIHJlY2lwZSB3YXMgd29ya2VkIG91dCBiZWZvcmUgaXQgbW92ZWQgaGVyZS5cbiAqXG4gKiBSZW1vdmluZyB0aGUgYmFuZCByYXRoZXIgdGhhbiBwaW5uaW5nIGl0IGFsc28gcmV0aXJlcyBldmVyeSBkZXZpY2Ugc2V0dGluZ1xuICogdGhhdCBwaW5uaW5nIG5lZWRlZCwgYW5kIG5vdGhpbmcgaW4gYSBzdGF0dXMgYmFyIGlzIGV2aWRlbmNlIGFib3V0IGEgcGx1Z2luLiBBXG4gKiBsaXN0aW5nIGZyYW1lIHdpdGhvdXQgb25lIGlzIHdoYXQgYSBsaXN0aW5nIGZyYW1lIG5vcm1hbGx5IGxvb2tzIGxpa2UuXG4gKlxuICogKipUaGUgYmFuZCdzIGhlaWdodCBpcyB0aGUgY2FsbGVyJ3MgdG8gbWVhc3VyZSwgYW5kIHRoZXJlIGlzIGRlbGliZXJhdGVseSBub1xuICogZGVmYXVsdC4qKiBBIHdyb25nIGhlaWdodCBkb2VzIG5vdCBmYWlsIGxvdWRseSBieSBpdHNlbGYgXHUyMDE0IGl0IHBhaW50cyBvdmVyIHRoZVxuICogcHJvZHVjdC4gV2hhdCBzdGFuZHMgYmV0d2VlbiB0aGUgdHdvIGlzIHtAbGluayByZXNvbHZlU3RhdHVzQmFyQmFuZH06IHRoZSByb3dcbiAqIGp1c3QgdW5kZXIgdGhlIGJhcidzIG93biBjb250ZW50IG11c3QgYmUgb25lIGZsYXQgY29sb3IgYWxsIHRoZSB3YXkgYWNyb3NzLFxuICogd2hpY2ggaXMgd2hhdCBhbiBlbXB0eSBiYW5kIGxvb2tzIGxpa2UgYW5kIGlzIGFsc28gd2hlcmUgdGhlIGZpbGwgY29sb3IgaXNcbiAqIHNhbXBsZWQgZnJvbS4gQSB0YWxsZXIgYmFyLCBvciBhbiBPYnNpZGlhbiB0aGF0IGRyYXdzIGhpZ2hlciwgZmFpbHMgdGhlXG4gKiBjYXB0dXJlIGluc3RlYWQuXG4gKlxuICogQ29tcG9zaXRpbmcgb3ZlciBhIGNhcHR1cmVkIGZyYW1lIGlzIG5vdCBhIG5ldyBsaWJlcnR5OiBgbGFiZWxTY3JlZW5zaG90YFxuICogYWxyZWFkeSBkcmF3cyB0aGUgY2FwdGlvbiBiYW5kIGFjcm9zcyB0aGUgYm90dG9tIG9mIHRoZSBzYW1lIGltYWdlLlxuICovXG5cbmltcG9ydCB0eXBlIHsgU2hhcnBDb21wb3NpdGVMYXllciB9IGZyb20gJy4vc2hhcnAtbG9hZGVyLm1qcyc7XG5cbmltcG9ydCB7IGltcG9ydFNoYXJwIH0gZnJvbSAnLi9zaGFycC1sb2FkZXIubWpzJztcblxuLyoqXG4gKiBQYXJhbWV0ZXJzIGZvciB7QGxpbmsgYnVpbGRTdGF0dXNCYXJCYW5kRmFpbHVyZU1lc3NhZ2V9LlxuICovXG5leHBvcnQgaW50ZXJmYWNlIEJ1aWxkU3RhdHVzQmFyQmFuZEZhaWx1cmVNZXNzYWdlUGFyYW1zIHtcbiAgLyoqXG4gICAqIEhvdyBmYXIgaW4gZnJvbSBlYWNoIGVkZ2UgdGhlIHJvdyB3YXMgc2Nhbm5lZCwgaW4gcGl4ZWxzLlxuICAgKi9cbiAgcmVhZG9ubHkgZWRnZU1hcmdpbkluUGl4ZWxzOiBudW1iZXI7XG5cbiAgLyoqXG4gICAqIFRoZSBoZWlnaHQgb2YgdGhlIGJhbmQgdGhhdCB3YXMgYWJvdXQgdG8gYmUgcGFpbnRlZCwgaW4gcGl4ZWxzLlxuICAgKi9cbiAgcmVhZG9ubHkgaGVpZ2h0SW5QaXhlbHM6IG51bWJlcjtcblxuICAvKipcbiAgICogVGhlIHZlcmRpY3QgYmVpbmcgcmVwb3J0ZWQuXG4gICAqL1xuICByZWFkb25seSB2ZXJkaWN0OiBTdGF0dXNCYXJCYW5kVmVyZGljdDtcblxuICAvKipcbiAgICogVGhlIGZyYW1lJ3Mgd2lkdGgsIGluIHBpeGVscy5cbiAgICovXG4gIHJlYWRvbmx5IHdpZHRoSW5QaXhlbHM6IG51bWJlcjtcbn1cblxuLyoqXG4gKiBPcHRpb25zIGZvciB7QGxpbmsgcGFpbnRPdXRTdGF0dXNCYXJ9LlxuICovXG5leHBvcnQgaW50ZXJmYWNlIFBhaW50T3V0U3RhdHVzQmFyT3B0aW9ucyB7XG4gIC8qKlxuICAgKiBIb3cgZmFyIGluIGZyb20gZWFjaCBlZGdlIHRoZSBjbGVhci1vZi1jaHJvbWUgY2hlY2sgcmVhZHMuXG4gICAqXG4gICAqIEBkZWZhdWx0IHtAbGluayBERUZBVUxUX1NUQVRVU19CQVJfRURHRV9NQVJHSU5fSU5fUElYRUxTfVxuICAgKi9cbiAgcmVhZG9ubHkgZWRnZU1hcmdpbkluUGl4ZWxzPzogbnVtYmVyO1xuXG4gIC8qKlxuICAgKiBIb3cgdGFsbCB0aGUgZGV2aWNlJ3Mgc3RhdHVzIGJhciBpcywgaW4gZnJhbWVidWZmZXIgcGl4ZWxzLlxuICAgKlxuICAgKiAqKk1lYXN1cmVkLCBuZXZlciBhc3N1bWVkLCBhbmQgZGVsaWJlcmF0ZWx5IHdpdGhvdXQgYSBkZWZhdWx0LioqIDI0ZHAgYXQgYVxuICAgKiBkZW5zaXR5IG9mIDMyMCBpcyA0OHB4LCBhbmQgdGhhdCBpcyBvbmUgQVZEJ3MgbnVtYmVyIHJhdGhlciB0aGFuIGEgY29uc3RhbnRcbiAgICogb2YgQW5kcm9pZC4gTWVhc3VyZSBpdCBvbiBhIGZyYW1lOiB0aGUgYmFyJ3Mgb3duIGNvbnRlbnQgb2NjdXBpZXMgdGhlIHRvcFxuICAgKiByb3dzLCBhIGZldyBlbXB0eSByb3dzIGZvbGxvdywgYW5kIHRoZSBhcHAncyB0b3AgY2hyb21lIHN0YXJ0cyBhdCBleGFjdGx5XG4gICAqIHRoaXMgb2Zmc2V0LlxuICAgKi9cbiAgcmVhZG9ubHkgaGVpZ2h0SW5QaXhlbHM6IG51bWJlcjtcblxuICAvKipcbiAgICogSG93IGZhciBhYm92ZSB0aGUgYmFuZCdzIGJvdHRvbSBlZGdlIHRoZSBmaWxsIGNvbG9yIGlzIHNhbXBsZWQsIGluIHBpeGVscy5cbiAgICpcbiAgICogQGRlZmF1bHQge0BsaW5rIERFRkFVTFRfU1RBVFVTX0JBUl9TQU1QTEVfSU5TRVRfSU5fUElYRUxTfVxuICAgKi9cbiAgcmVhZG9ubHkgc2FtcGxlSW5zZXRJblBpeGVscz86IG51bWJlcjtcbn1cblxuLyoqXG4gKiBQYXJhbWV0ZXJzIGZvciB7QGxpbmsgcmVzb2x2ZVN0YXR1c0JhckJhbmR9LlxuICovXG5leHBvcnQgaW50ZXJmYWNlIFJlc29sdmVTdGF0dXNCYXJCYW5kUGFyYW1zIHtcbiAgLyoqXG4gICAqIEhvdyBtYW55IGJ5dGVzIGVhY2ggcGl4ZWwgb2NjdXBpZXMgaW4ge0BsaW5rIFJlc29sdmVTdGF0dXNCYXJCYW5kUGFyYW1zLnBpeGVsc30gXHUyMDE0IDMgZm9yIFJHQiwgNCBmb3IgUkdCQS5cbiAgICovXG4gIHJlYWRvbmx5IGNoYW5uZWxDb3VudDogbnVtYmVyO1xuXG4gIC8qKlxuICAgKiBIb3cgZmFyIGluIGZyb20gZWFjaCBlZGdlIHRvIHN0YXJ0IHJlYWRpbmcsIGluIHBpeGVscy5cbiAgICpcbiAgICogQSBkZXZpY2UncyByb3VuZGVkIGNvcm5lcnMgZGFya2VuIGEgaGFuZGZ1bCBvZiBwaXhlbHMgYXQgYm90aCBlbmRzIG9mIGV2ZXJ5XG4gICAqIHJvdyBpbiB0aGUgYmFuZCwgY29uc3RhbnRseSBhbmQgaGFybWxlc3NseTsgdGhlIGNoZWNrIGlzIGFib3V0IHdoYXQgdGhlIEFQUFxuICAgKiBkcmF3cywgc28gaXQgaWdub3JlcyB0aGVtLlxuICAgKlxuICAgKiBAZGVmYXVsdCB7QGxpbmsgREVGQVVMVF9TVEFUVVNfQkFSX0VER0VfTUFSR0lOX0lOX1BJWEVMU31cbiAgICovXG4gIHJlYWRvbmx5IGVkZ2VNYXJnaW5JblBpeGVscz86IG51bWJlcjtcblxuICAvKipcbiAgICogVGhlIGhlaWdodCBvZiB0aGUgYmFuZCwgaW4gcGl4ZWxzIFx1MjAxNCB0aGUgc2FtZSBudW1iZXIge0BsaW5rIHBhaW50T3V0U3RhdHVzQmFyfSB3b3VsZCBwYWludC5cbiAgICovXG4gIHJlYWRvbmx5IGhlaWdodEluUGl4ZWxzOiBudW1iZXI7XG5cbiAgLyoqXG4gICAqIFRoZSBmcmFtZSdzIHJhdyBwaXhlbHMsIHJvdy1tYWpvciwgYXMgYHNoYXJwYCdzIGByYXcoKWAgcmVwb3J0cyB0aGVtLlxuICAgKi9cbiAgcmVhZG9ubHkgcGl4ZWxzOiBVaW50OEFycmF5O1xuXG4gIC8qKlxuICAgKiBIb3cgZmFyIGFib3ZlIHRoZSBiYW5kJ3MgYm90dG9tIGVkZ2UgdGhlIHJvdyBpcyByZWFkIGZyb20sIGluIHBpeGVscy5cbiAgICpcbiAgICogVGhlIGJhcidzIG93biBjb250ZW50IHN0b3BzIHNob3J0IG9mIHRoZSBiYW5kJ3MgYm90dG9tIGVkZ2UsIHNvIHRoZSByb3dzXG4gICAqIGltbWVkaWF0ZWx5IHVuZGVyIGl0IGFyZSBlbXB0eTsgdGhpcyBwaWNrcyBvbmUgb2YgdGhlbS5cbiAgICpcbiAgICogQGRlZmF1bHQge0BsaW5rIERFRkFVTFRfU1RBVFVTX0JBUl9TQU1QTEVfSU5TRVRfSU5fUElYRUxTfVxuICAgKi9cbiAgcmVhZG9ubHkgc2FtcGxlSW5zZXRJblBpeGVscz86IG51bWJlcjtcblxuICAvKipcbiAgICogVGhlIGZyYW1lJ3Mgd2lkdGgsIGluIHBpeGVscy5cbiAgICovXG4gIHJlYWRvbmx5IHdpZHRoSW5QaXhlbHM6IG51bWJlcjtcbn1cblxuLyoqXG4gKiBBIGNvbG9yIHJlYWQgb3V0IG9mIGEgZnJhbWUsIGNoYW5uZWwgYnkgY2hhbm5lbC5cbiAqXG4gKiBUaGlzIHBhY2thZ2UncyBvd24gdHlwZSByYXRoZXIgdGhhbiBgc2hhcnBgJ3MsIGJlY2F1c2UgaXQgaXMgcGFydCBvZiB0aGVcbiAqIHB1YmxpYyBzdXJmYWNlIGFuZCBgc2hhcnBgIGlzIGFuIE9QVElPTkFMIHBlZXI6IGEgY29uc3VtZXIgdGhhdCBuZXZlciBpbnN0YWxsc1xuICogaXQgc3RpbGwgbmFtZXMgdGhpcy4gU3RydWN0dXJhbGx5IHdoYXQgYHNoYXJwYCdzIGBiYWNrZ3JvdW5kYCB0YWtlcywgd2hpY2ggaXNcbiAqIGhvdyB0aGUgZmlsbCBibG9jayBpcyBjcmVhdGVkIGZyb20gb25lIHdpdGggbm8gY29udmVyc2lvbiBpbiBiZXR3ZWVuLlxuICovXG5leHBvcnQgaW50ZXJmYWNlIFN0YXR1c0JhckJhbmRDb2xvciB7XG4gIC8qKlxuICAgKiBUaGUgYmx1ZSBjaGFubmVsLCAwLTI1NS5cbiAgICovXG4gIHJlYWRvbmx5IGI6IG51bWJlcjtcblxuICAvKipcbiAgICogVGhlIGdyZWVuIGNoYW5uZWwsIDAtMjU1LlxuICAgKi9cbiAgcmVhZG9ubHkgZzogbnVtYmVyO1xuXG4gIC8qKlxuICAgKiBUaGUgcmVkIGNoYW5uZWwsIDAtMjU1LlxuICAgKi9cbiAgcmVhZG9ubHkgcjogbnVtYmVyO1xufVxuXG4vKipcbiAqIFdoYXQgdGhlIHJvdyB1bmRlciB0aGUgc3RhdHVzIGJhcidzIGNvbnRlbnQgdHVybmVkIG91dCB0byBob2xkLlxuICpcbiAqIFZlcmRpY3QtYXMtZGF0YSwgbGlrZSB0aGUgY29tcGF0aWJpbGl0eSBhbmQgdGVhcmRvd24gY2hlY2tzOiB0aGUgcmVhZGluZyBhbmRcbiAqIHRoZSB0aHJvd2luZyBhcmUgc2VwYXJhdGUsIHNvIGEgY2FsbGVyIGNhbiBhc2sgd2hhdCBhIGZyYW1lIGxvb2tzIGxpa2Ugd2l0aG91dFxuICogaGF2aW5nIHRvIGNhdGNoIGFuIGVycm9yIHRvIGZpbmQgb3V0LlxuICovXG5leHBvcnQgaW50ZXJmYWNlIFN0YXR1c0JhckJhbmRWZXJkaWN0IHtcbiAgLyoqXG4gICAqIFRoZSBjb2xvciB0aGUgYmFuZCB3b3VsZCBiZSBmaWxsZWQgd2l0aCBcdTIwMTQgcmVhZCBmcm9tIHRoZSBmaXJzdCBzY2FubmVkIGNvbHVtbiBvZiB0aGUgc2FtcGxlIHJvdy5cbiAgICovXG4gIHJlYWRvbmx5IGJhY2tncm91bmRDb2xvcjogU3RhdHVzQmFyQmFuZENvbG9yO1xuXG4gIC8qKlxuICAgKiBIb3cgbWFueSBkaXN0aW5jdCBjb2xvcnMgdGhlIHNjYW5uZWQgcm93IGhvbGRzLiBPbmUgaXMgd2hhdCBhbiBlbXB0eSBiYW5kIGxvb2tzIGxpa2UuXG4gICAqL1xuICByZWFkb25seSBkaXN0aW5jdENvbG9yQ291bnQ6IG51bWJlcjtcblxuICAvKipcbiAgICogVGhlIGNvbG9yIGF0IHtAbGluayBTdGF0dXNCYXJCYW5kVmVyZGljdC5maXJzdERpZmZlcmVudENvbHVtblhJblBpeGVsc30sIG9yIGBudWxsYCB3aGVuIHRoZSByb3cgaXMgZmxhdC5cbiAgICovXG4gIHJlYWRvbmx5IGZpcnN0RGlmZmVyZW50Q29sb3I6IG51bGwgfCBTdGF0dXNCYXJCYW5kQ29sb3I7XG5cbiAgLyoqXG4gICAqIFRoZSBmaXJzdCBzY2FubmVkIGNvbHVtbiB0aGF0IGRpZmZlcnMgZnJvbSB7QGxpbmsgU3RhdHVzQmFyQmFuZFZlcmRpY3QuYmFja2dyb3VuZENvbG9yfSwgb3IgYG51bGxgIHdoZW4gdGhlIHJvdyBpcyBmbGF0LlxuICAgKi9cbiAgcmVhZG9ubHkgZmlyc3REaWZmZXJlbnRDb2x1bW5YSW5QaXhlbHM6IG51bGwgfCBudW1iZXI7XG5cbiAgLyoqXG4gICAqIFdoZXRoZXIgdGhlIHJvdyBpcyBvbmUgZmxhdCBjb2xvciwgYW5kIHNvIHdoZXRoZXIgdGhlIGJhbmQgaG9sZHMgbm90aGluZyBidXQgZGV2aWNlIGNocm9tZS5cbiAgICovXG4gIHJlYWRvbmx5IGlzQ2xlYXI6IGJvb2xlYW47XG5cbiAgLyoqXG4gICAqIFdoaWNoIHJvdyB3YXMgcmVhZCwgYXMgYSBkaXN0YW5jZSBmcm9tIHRoZSB0b3Agb2YgdGhlIGZyYW1lIGluIHBpeGVscy5cbiAgICovXG4gIHJlYWRvbmx5IHNhbXBsZVJvd1lJblBpeGVsczogbnVtYmVyO1xufVxuXG4vKipcbiAqIEhvdyBmYXIgaW4gZnJvbSBlYWNoIGVkZ2UgdGhlIGNsZWFyLW9mLWNocm9tZSBjaGVjayByZWFkcywgaW4gcGl4ZWxzLlxuICpcbiAqIEV4cG9ydGVkIGJlY2F1c2UgYSBjYWxsZXIgdGlnaHRlbmluZyBvciBsb29zZW5pbmcgaXQgd2FudHMgdG8gc2F5IHNvIHJlbGF0aXZlXG4gKiB0byB0aGlzIHJhdGhlciB0aGFuIGluIHRoZSBhYnN0cmFjdC5cbiAqL1xuZXhwb3J0IGNvbnN0IERFRkFVTFRfU1RBVFVTX0JBUl9FREdFX01BUkdJTl9JTl9QSVhFTFMgPSAxMDA7XG5cbi8qKlxuICogSG93IGZhciBhYm92ZSB0aGUgYmFuZCdzIGJvdHRvbSBlZGdlIHRoZSBzYW1wbGUgcm93IHNpdHMsIGluIHBpeGVscy5cbiAqXG4gKiBGb3VyIHJvd3MgdXA6IGZhciBlbm91Z2ggdG8gY2xlYXIgdGhlIGJhcidzIG93biBjb250ZW50LCBjbG9zZSBlbm91Z2ggdG8gc3RpbGxcbiAqIGJlIGluc2lkZSB0aGUgYmFuZCBvbiBldmVyeSBkZXZpY2UgdGhpcyBoYXMgYmVlbiBtZWFzdXJlZCBvbi5cbiAqL1xuZXhwb3J0IGNvbnN0IERFRkFVTFRfU1RBVFVTX0JBUl9TQU1QTEVfSU5TRVRfSU5fUElYRUxTID0gNDtcblxuLyoqXG4gKiBCeXRlIG9mZnNldCBvZiB0aGUgYmx1ZSBjaGFubmVsIHdpdGhpbiBvbmUgcGl4ZWwgb2YgYSByYXcgZnJhbWUuXG4gKi9cbmNvbnN0IEJMVUVfQ0hBTk5FTF9PRkZTRVQgPSAyO1xuXG4vKipcbiAqIEhvdyBtYW55IGNoYW5uZWxzIHRoZSBmaWxsIGJsb2NrIGlzIGNyZWF0ZWQgd2l0aCBcdTIwMTQgUkdCLCB3aXRoIG5vIGFscGhhLCBiZWNhdXNlIGl0IGlzIG9wYXF1ZSBieSBjb25zdHJ1Y3Rpb24uXG4gKi9cbmNvbnN0IEZJTExfQ0hBTk5FTF9DT1VOVCA9IDM7XG5cbi8qKlxuICogQnl0ZSBvZmZzZXQgb2YgdGhlIGdyZWVuIGNoYW5uZWwgd2l0aGluIG9uZSBwaXhlbCBvZiBhIHJhdyBmcmFtZS5cbiAqL1xuY29uc3QgR1JFRU5fQ0hBTk5FTF9PRkZTRVQgPSAxO1xuXG4vKipcbiAqIFRoZSBmZXdlc3QgY29sdW1ucyBhIG1lYW5pbmdmdWwgc2NhbiBjYW4gY292ZXIuXG4gKlxuICogT25lIGNvbHVtbiBpcyB0cml2aWFsbHkgZmxhdCwgc28gYSBtYXJnaW4gdGhhdCBsZWF2ZXMgZmV3ZXIgdGhhbiB0d28gaXMgYVxuICogY2hlY2sgdGhhdCBjYW5ub3QgZmFpbCBcdTIwMTQgdGhlIGV4YWN0IHNoYXBlIHRoaXMgaGVscGVyIGV4aXN0cyB0byBhdm9pZC5cbiAqL1xuY29uc3QgTUlOSU1VTV9TQ0FOTkVEX0NPTFVNTl9DT1VOVCA9IDI7XG5cbi8qKlxuICogQnl0ZSBvZmZzZXQgb2YgdGhlIHJlZCBjaGFubmVsIHdpdGhpbiBvbmUgcGl4ZWwgb2YgYSByYXcgZnJhbWUuXG4gKi9cbmNvbnN0IFJFRF9DSEFOTkVMX09GRlNFVCA9IDA7XG5cbi8qKlxuICogQnVpbGRzIHRoZSBtZXNzYWdlIGEgYmFuZCB0aGF0IGlzIG5vdCBjbGVhciBpcyByZWZ1c2VkIHdpdGguXG4gKlxuICogU2VwYXJhdGUgZnJvbSB0aGUgdGhyb3dpbmcgc28gaXQgY2FuIGJlIGFzc2VydGVkIG9uIGRpcmVjdGx5OiB3aGF0IG1ha2VzIHRoaXNcbiAqIGZhaWx1cmUgYWN0aW9uYWJsZSBpcyB0aGUgbWVhc3VyZW1lbnQsIG5vdCB0aGUgd29yZGluZyBhcm91bmQgaXQuXG4gKlxuICogQHBhcmFtIHBhcmFtcyAtIFRoZSB2ZXJkaWN0IGFuZCB0aGUgZ2VvbWV0cnkgaXQgd2FzIHJlYWQgd2l0aC5cbiAqIEByZXR1cm5zIFRoZSBtZXNzYWdlLCByZWFkeSB0byB0aHJvdy5cbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIGJ1aWxkU3RhdHVzQmFyQmFuZEZhaWx1cmVNZXNzYWdlKHBhcmFtczogQnVpbGRTdGF0dXNCYXJCYW5kRmFpbHVyZU1lc3NhZ2VQYXJhbXMpOiBzdHJpbmcge1xuICBjb25zdCB7XG4gICAgZWRnZU1hcmdpbkluUGl4ZWxzLFxuICAgIGhlaWdodEluUGl4ZWxzLFxuICAgIHZlcmRpY3QsXG4gICAgd2lkdGhJblBpeGVsc1xuICB9ID0gcGFyYW1zO1xuXG4gIGNvbnN0IGxpbmVzID0gW1xuICAgICdwYWludE91dFN0YXR1c0JhcjogdGhlIHJvdyB1bmRlciB0aGUgc3RhdHVzIGJhciBob2xkcyBtb3JlIHRoYW4gb25lIGNvbG9yLCBzbyBhIGJhbmQgb2YgJ1xuICAgICsgYCR7U3RyaW5nKGhlaWdodEluUGl4ZWxzKX1weCB3b3VsZCBwYWludCBvdmVyIHRoZSBhcHAgcmF0aGVyIHRoYW4gb3ZlciB0aGUgZGV2aWNlLmAsXG4gICAgYGJhbmQ9JHtTdHJpbmcoaGVpZ2h0SW5QaXhlbHMpfXB4IHNhbXBsZVJvdz15JHtTdHJpbmcodmVyZGljdC5zYW1wbGVSb3dZSW5QaXhlbHMpfSBgXG4gICAgKyBgc2Nhbm5lZCB4JHtTdHJpbmcoZWRnZU1hcmdpbkluUGl4ZWxzKX0uLiR7U3RyaW5nKHdpZHRoSW5QaXhlbHMgLSBlZGdlTWFyZ2luSW5QaXhlbHMpfSBgXG4gICAgKyBgb2YgJHtTdHJpbmcod2lkdGhJblBpeGVscyl9cHggZGlzdGluY3RDb2xvcnM9JHtTdHJpbmcodmVyZGljdC5kaXN0aW5jdENvbG9yQ291bnQpfWAsXG4gICAgYHNhbXBsZWQgJHtmb3JtYXRDb2xvcih2ZXJkaWN0LmJhY2tncm91bmRDb2xvcil9LCBidXQgeD0ke1N0cmluZyh2ZXJkaWN0LmZpcnN0RGlmZmVyZW50Q29sdW1uWEluUGl4ZWxzKX0gYFxuICAgICsgYGlzICR7Zm9ybWF0Q29sb3IodmVyZGljdC5maXJzdERpZmZlcmVudENvbG9yKX1gLFxuICAgICdBIHRhbGxlciBzdGF0dXMgYmFyLCBvciBhbiBPYnNpZGlhbiB0aGF0IGRyYXdzIGhpZ2hlciwgYm90aCBsb29rIGxpa2UgdGhpcy4gTWVhc3VyZSB0aGUgYmFuZCBvbiBhIGZyZXNoICdcbiAgICArICdmcmFtZSBhbmQgcGFzcyB0aGUgaGVpZ2h0IHlvdSBtZWFzdXJlZDogZW5sYXJnaW5nIGl0IHVudGlsIHRoZSBjaGVjayBwYXNzZXMgaXMgaG93IGEgY2FwdHVyZSBwYWludHMgJ1xuICAgICsgJ292ZXIgdGhlIHByb2R1Y3QuJ1xuICBdO1xuXG4gIHJldHVybiBsaW5lcy5qb2luKCdcXG4nKTtcbn1cblxuLyoqXG4gKiBGaWxscyB0aGUgc3RhdHVzLWJhciBiYW5kIHdpdGggdGhlIGJhY2tncm91bmQgYmVoaW5kIGl0LCBzbyB0aGUgZnJhbWUgY2FycmllcyBubyBkZXZpY2UgY2hyb21lLlxuICpcbiAqIFRoZSBpbWFnZSBrZWVwcyBpdHMgZGltZW5zaW9ucyBleGFjdGx5OiB0aGUgYmFuZCBpcyBjb21wb3NpdGVkIE9WRVIgdGhlIGZyYW1lLFxuICogbmV2ZXIgY3JvcHBlZCBvZmYgaXQsIGJlY2F1c2UgYSBzdG9yZSBsaXN0aW5nIGV4cGVjdHMgYSBzcGVjaWZpYyBzaXplIGFuZCBhXG4gKiBjcm9wcGVkIGZyYW1lIGlzIGEgZGlmZmVyZW50IHBpY3R1cmUuXG4gKlxuICogQHBhcmFtIGJ5dGVzIC0gVGhlIGRldmljZSBmcmFtZWJ1ZmZlciwgYXMgYGNhcHR1cmVEZXZpY2VTY3JlZW5zaG90YCByZXR1cm5lZCBpdC5cbiAqIEBwYXJhbSBvcHRpb25zIC0gSG93IHRhbGwgdGhlIGJhbmQgaXMsIGFuZCBob3cgdGhlIGNoZWNrIHJlYWRzIGl0LlxuICogQHJldHVybnMgQSB7QGxpbmsgUHJvbWlzZX0gdGhhdCByZXNvbHZlcyB0byB0aGUgc2FtZSBmcmFtZSB3aXRoIHRoZSBiYW5kIHBhaW50ZWQgb3V0LCBhcyBQTkcgYnl0ZXMuXG4gKiBAdGhyb3dzIEVycm9yIGlmIGBzaGFycGAgaXMgbm90IGluc3RhbGxlZCwgdGhlIGJhbmQgaXMgYXMgdGFsbCBhcyB0aGUgZnJhbWUgb3IgdGFsbGVyLCBvciB0aGUgcm93IHVuZGVyXG4gKiAgIHRoZSBiYXIncyBjb250ZW50IGlzIG5vdCBvbmUgZmxhdCBjb2xvciBcdTIwMTQgc2VlIHtAbGluayBidWlsZFN0YXR1c0JhckJhbmRGYWlsdXJlTWVzc2FnZX0uXG4gKi9cbmV4cG9ydCBhc3luYyBmdW5jdGlvbiBwYWludE91dFN0YXR1c0JhcihieXRlczogVWludDhBcnJheSwgb3B0aW9uczogUGFpbnRPdXRTdGF0dXNCYXJPcHRpb25zKTogUHJvbWlzZTxVaW50OEFycmF5PiB7XG4gIGNvbnN0IHsgaGVpZ2h0SW5QaXhlbHMgfSA9IG9wdGlvbnM7XG4gIGNvbnN0IGVkZ2VNYXJnaW5JblBpeGVscyA9IG9wdGlvbnMuZWRnZU1hcmdpbkluUGl4ZWxzID8/IERFRkFVTFRfU1RBVFVTX0JBUl9FREdFX01BUkdJTl9JTl9QSVhFTFM7XG4gIGNvbnN0IHNoYXJwID0gYXdhaXQgaW1wb3J0U2hhcnAoJ3BhaW50T3V0U3RhdHVzQmFyJyk7XG4gIGNvbnN0IHsgZGF0YSwgaW5mbyB9ID0gYXdhaXQgc2hhcnAoYnl0ZXMpXG4gICAgLmVuc3VyZUFscGhhKClcbiAgICAucmF3KClcbiAgICAudG9CdWZmZXIoeyByZXNvbHZlV2l0aE9iamVjdDogdHJ1ZSB9KTtcblxuICBpZiAoaGVpZ2h0SW5QaXhlbHMgPj0gaW5mby5oZWlnaHQpIHtcbiAgICB0aHJvdyBuZXcgRXJyb3IoXG4gICAgICBgcGFpbnRPdXRTdGF0dXNCYXI6IGEgYmFuZCBvZiAke1N0cmluZyhoZWlnaHRJblBpeGVscyl9cHggd291bGQgY292ZXIgYSBmcmFtZSB0aGF0IGlzIG9ubHkgYFxuICAgICAgICArIGAke1N0cmluZyhpbmZvLmhlaWdodCl9cHggdGFsbC4gVGhhdCBpcyBhIG1lYXN1cmVtZW50IG9mIHNvbWUgb3RoZXIgZGV2aWNlLCBub3Qgb2YgdGhpcyBmcmFtZS5gXG4gICAgKTtcbiAgfVxuXG4gIGNvbnN0IHZlcmRpY3QgPSByZXNvbHZlU3RhdHVzQmFyQmFuZCh7XG4gICAgY2hhbm5lbENvdW50OiBpbmZvLmNoYW5uZWxzLFxuICAgIGVkZ2VNYXJnaW5JblBpeGVscyxcbiAgICBoZWlnaHRJblBpeGVscyxcbiAgICBwaXhlbHM6IGRhdGEsXG4gICAgLi4uKG9wdGlvbnMuc2FtcGxlSW5zZXRJblBpeGVscyAhPT0gdW5kZWZpbmVkICYmIHsgc2FtcGxlSW5zZXRJblBpeGVsczogb3B0aW9ucy5zYW1wbGVJbnNldEluUGl4ZWxzIH0pLFxuICAgIHdpZHRoSW5QaXhlbHM6IGluZm8ud2lkdGhcbiAgfSk7XG5cbiAgaWYgKCF2ZXJkaWN0LmlzQ2xlYXIpIHtcbiAgICB0aHJvdyBuZXcgRXJyb3IoYnVpbGRTdGF0dXNCYXJCYW5kRmFpbHVyZU1lc3NhZ2Uoe1xuICAgICAgZWRnZU1hcmdpbkluUGl4ZWxzLFxuICAgICAgaGVpZ2h0SW5QaXhlbHMsXG4gICAgICB2ZXJkaWN0LFxuICAgICAgd2lkdGhJblBpeGVsczogaW5mby53aWR0aFxuICAgIH0pKTtcbiAgfVxuXG4gIGNvbnN0IGxheWVyOiBTaGFycENvbXBvc2l0ZUxheWVyID0ge1xuICAgIGlucHV0OiB7XG4gICAgICBjcmVhdGU6IHtcbiAgICAgICAgYmFja2dyb3VuZDogdmVyZGljdC5iYWNrZ3JvdW5kQ29sb3IsXG4gICAgICAgIGNoYW5uZWxzOiBGSUxMX0NIQU5ORUxfQ09VTlQsXG4gICAgICAgIGhlaWdodDogaGVpZ2h0SW5QaXhlbHMsXG4gICAgICAgIHdpZHRoOiBpbmZvLndpZHRoXG4gICAgICB9XG4gICAgfSxcbiAgICBsZWZ0OiAwLFxuICAgIHRvcDogMFxuICB9O1xuXG4gIGNvbnN0IHBhaW50ZWQgPSBhd2FpdCBzaGFycChieXRlcylcbiAgICAuY29tcG9zaXRlKFtsYXllcl0pXG4gICAgLnBuZygpXG4gICAgLnRvQnVmZmVyKCk7XG5cbiAgcmV0dXJuIG5ldyBVaW50OEFycmF5KHBhaW50ZWQpO1xufVxuXG4vKipcbiAqIFJlYWRzIHRoZSByb3cgdW5kZXIgdGhlIHN0YXR1cyBiYXIncyBjb250ZW50IGFuZCBzYXlzIHdoZXRoZXIgdGhlIGJhbmQgaXMgY2xlYXIgb2YgdGhlIGFwcC5cbiAqXG4gKiBUaGUgYmFuZCdzIGhlaWdodCBpcyBhIG51bWJlciBhIGNhbGxlciBtZWFzdXJlZCBvbmNlLCBhbmQgYSBtZWFzdXJlZCBudW1iZXJcbiAqIGNhbiBnbyBzdGFsZSBcdTIwMTQgYSB0YWxsZXIgc3RhdHVzIGJhciwgb3IgYW4gT2JzaWRpYW4gdGhhdCBkcmF3cyBoaWdoZXIsIHdvdWxkXG4gKiBoYXZlIHRoZSBjYXB0dXJlIHF1aWV0bHkgcGFpbnQgb3ZlciB0aGUgcHJvZHVjdC4gU28gdGhlIHJvdyBqdXN0IGJlbG93IHRoZVxuICogYmFyJ3Mgb3duIGNvbnRlbnQgaXMgcmVxdWlyZWQgdG8gYmUgb25lIGZsYXQgY29sb3IgYWxsIHRoZSB3YXkgYWNyb3NzOiB0aGF0XG4gKiBpcyB3aGF0IGFuIGVtcHR5IGJhbmQgbG9va3MgbGlrZSwgYW5kIGl0IGlzIHdoYXQgdGhlIGZpbGwgY29sb3IgaXMgcmVhZCBmcm9tLlxuICpcbiAqIEBwYXJhbSBwYXJhbXMgLSBUaGUgZnJhbWUncyByYXcgcGl4ZWxzIGFuZCB0aGUgYmFuZCdzIGdlb21ldHJ5LlxuICogQHJldHVybnMgV2hhdCB0aGUgc2Nhbm5lZCByb3cgaG9sZHMuXG4gKiBAdGhyb3dzIEVycm9yIGlmIHRoZSBnZW9tZXRyeSBpcyBkZWdlbmVyYXRlIFx1MjAxNCBhIG5vbi1wb3NpdGl2ZSBkaW1lbnNpb24sIGEgc2FtcGxlIHJvdyBhYm92ZSB0aGUgdG9wIG9mXG4gKiAgIHRoZSBmcmFtZSwgYSBtYXJnaW4gdGhhdCBsZWF2ZXMgbm90aGluZyB0byBzY2FuLCBvciBhIGJ1ZmZlciB0b28gc2hvcnQgdG8gaG9sZCB0aGUgcm93LlxuICovXG5leHBvcnQgZnVuY3Rpb24gcmVzb2x2ZVN0YXR1c0JhckJhbmQocGFyYW1zOiBSZXNvbHZlU3RhdHVzQmFyQmFuZFBhcmFtcyk6IFN0YXR1c0JhckJhbmRWZXJkaWN0IHtcbiAgY29uc3Qge1xuICAgIGNoYW5uZWxDb3VudCxcbiAgICBoZWlnaHRJblBpeGVscyxcbiAgICBwaXhlbHMsXG4gICAgd2lkdGhJblBpeGVsc1xuICB9ID0gcGFyYW1zO1xuICBjb25zdCBlZGdlTWFyZ2luSW5QaXhlbHMgPSBwYXJhbXMuZWRnZU1hcmdpbkluUGl4ZWxzID8/IERFRkFVTFRfU1RBVFVTX0JBUl9FREdFX01BUkdJTl9JTl9QSVhFTFM7XG4gIGNvbnN0IHNhbXBsZUluc2V0SW5QaXhlbHMgPSBwYXJhbXMuc2FtcGxlSW5zZXRJblBpeGVscyA/PyBERUZBVUxUX1NUQVRVU19CQVJfU0FNUExFX0lOU0VUX0lOX1BJWEVMUztcblxuICBhc3NlcnRQb3NpdGl2ZSgnd2lkdGhJblBpeGVscycsIHdpZHRoSW5QaXhlbHMpO1xuICBhc3NlcnRQb3NpdGl2ZSgnaGVpZ2h0SW5QaXhlbHMnLCBoZWlnaHRJblBpeGVscyk7XG4gIGFzc2VydFBvc2l0aXZlKCdjaGFubmVsQ291bnQnLCBjaGFubmVsQ291bnQpO1xuXG4gIGNvbnN0IHNhbXBsZVJvd1lJblBpeGVscyA9IGhlaWdodEluUGl4ZWxzIC0gc2FtcGxlSW5zZXRJblBpeGVscztcblxuICBpZiAoc2FtcGxlUm93WUluUGl4ZWxzIDwgMCkge1xuICAgIHRocm93IG5ldyBFcnJvcihcbiAgICAgIGByZXNvbHZlU3RhdHVzQmFyQmFuZDogYSBzYW1wbGUgaW5zZXQgb2YgJHtTdHJpbmcoc2FtcGxlSW5zZXRJblBpeGVscyl9cHggcHV0cyB0aGUgc2FtcGxlIHJvdyBhYm92ZSB0aGUgdG9wIGBcbiAgICAgICAgKyBgb2YgdGhlIGZyYW1lIGZvciBhIGJhbmQgb2YgJHtTdHJpbmcoaGVpZ2h0SW5QaXhlbHMpfXB4LmBcbiAgICApO1xuICB9XG5cbiAgY29uc3QgbGFzdFNjYW5uZWRDb2x1bW5YSW5QaXhlbHMgPSB3aWR0aEluUGl4ZWxzIC0gZWRnZU1hcmdpbkluUGl4ZWxzO1xuXG4gIGlmIChsYXN0U2Nhbm5lZENvbHVtblhJblBpeGVscyAtIGVkZ2VNYXJnaW5JblBpeGVscyA8IE1JTklNVU1fU0NBTk5FRF9DT0xVTU5fQ09VTlQpIHtcbiAgICB0aHJvdyBuZXcgRXJyb3IoXG4gICAgICBgcmVzb2x2ZVN0YXR1c0JhckJhbmQ6IGFuIGVkZ2UgbWFyZ2luIG9mICR7U3RyaW5nKGVkZ2VNYXJnaW5JblBpeGVscyl9cHggbGVhdmVzIGZld2VyIHRoYW4gYFxuICAgICAgICArIGAke1N0cmluZyhNSU5JTVVNX1NDQU5ORURfQ09MVU1OX0NPVU5UKX0gY29sdW1ucyBvZiBhICR7U3RyaW5nKHdpZHRoSW5QaXhlbHMpfXB4IGZyYW1lIHRvIHNjYW4sIGBcbiAgICAgICAgKyAnd2hpY2ggaXMgYSBjaGVjayB0aGF0IGNhbm5vdCBmYWlsLidcbiAgICApO1xuICB9XG5cbiAgY29uc3Qgcm93U3RhcnRPZmZzZXQgPSBzYW1wbGVSb3dZSW5QaXhlbHMgKiB3aWR0aEluUGl4ZWxzICogY2hhbm5lbENvdW50O1xuICBjb25zdCByZXF1aXJlZExlbmd0aCA9IHJvd1N0YXJ0T2Zmc2V0ICsgd2lkdGhJblBpeGVscyAqIGNoYW5uZWxDb3VudDtcblxuICBpZiAocGl4ZWxzLmxlbmd0aCA8IHJlcXVpcmVkTGVuZ3RoKSB7XG4gICAgdGhyb3cgbmV3IEVycm9yKFxuICAgICAgYHJlc29sdmVTdGF0dXNCYXJCYW5kOiB0aGUgZnJhbWUgaG9sZHMgJHtTdHJpbmcocGl4ZWxzLmxlbmd0aCl9IGJ5dGVzLCB0b28gZmV3IGZvciByb3cgYFxuICAgICAgICArIGB5JHtTdHJpbmcoc2FtcGxlUm93WUluUGl4ZWxzKX0gb2YgYSAke1N0cmluZyh3aWR0aEluUGl4ZWxzKX1weC13aWRlIGZyYW1lIGF0IGBcbiAgICAgICAgKyBgJHtTdHJpbmcoY2hhbm5lbENvdW50KX0gY2hhbm5lbHMsIHdoaWNoIG5lZWRzICR7U3RyaW5nKHJlcXVpcmVkTGVuZ3RoKX0uYFxuICAgICk7XG4gIH1cblxuICBjb25zdCBiYWNrZ3JvdW5kQ29sb3IgPSByZWFkQ29sb3IocGl4ZWxzLCByb3dTdGFydE9mZnNldCArIGVkZ2VNYXJnaW5JblBpeGVscyAqIGNoYW5uZWxDb3VudCk7XG4gIGNvbnN0IGJhY2tncm91bmRLZXkgPSBmb3JtYXRDb2xvcihiYWNrZ3JvdW5kQ29sb3IpO1xuICBjb25zdCBjb2xvcktleXMgPSBuZXcgU2V0PHN0cmluZz4oKTtcbiAgbGV0IGZpcnN0RGlmZmVyZW50Q29sb3I6IG51bGwgfCBTdGF0dXNCYXJCYW5kQ29sb3IgPSBudWxsO1xuICBsZXQgZmlyc3REaWZmZXJlbnRDb2x1bW5YSW5QaXhlbHM6IG51bGwgfCBudW1iZXIgPSBudWxsO1xuXG4gIGZvciAobGV0IHggPSBlZGdlTWFyZ2luSW5QaXhlbHM7IHggPCBsYXN0U2Nhbm5lZENvbHVtblhJblBpeGVsczsgeCsrKSB7XG4gICAgY29uc3QgY29sb3IgPSByZWFkQ29sb3IocGl4ZWxzLCByb3dTdGFydE9mZnNldCArIHggKiBjaGFubmVsQ291bnQpO1xuICAgIGNvbnN0IGNvbG9yS2V5ID0gZm9ybWF0Q29sb3IoY29sb3IpO1xuICAgIGNvbG9yS2V5cy5hZGQoY29sb3JLZXkpO1xuXG4gICAgaWYgKGZpcnN0RGlmZmVyZW50Q29sdW1uWEluUGl4ZWxzICE9PSBudWxsIHx8IGNvbG9yS2V5ID09PSBiYWNrZ3JvdW5kS2V5KSB7XG4gICAgICBjb250aW51ZTtcbiAgICB9XG5cbiAgICBmaXJzdERpZmZlcmVudENvbG9yID0gY29sb3I7XG4gICAgZmlyc3REaWZmZXJlbnRDb2x1bW5YSW5QaXhlbHMgPSB4O1xuICB9XG5cbiAgcmV0dXJuIHtcbiAgICBiYWNrZ3JvdW5kQ29sb3IsXG4gICAgZGlzdGluY3RDb2xvckNvdW50OiBjb2xvcktleXMuc2l6ZSxcbiAgICBmaXJzdERpZmZlcmVudENvbG9yLFxuICAgIGZpcnN0RGlmZmVyZW50Q29sdW1uWEluUGl4ZWxzLFxuICAgIGlzQ2xlYXI6IGNvbG9yS2V5cy5zaXplID09PSAxLFxuICAgIHNhbXBsZVJvd1lJblBpeGVsc1xuICB9O1xufVxuXG4vKipcbiAqIFJlZnVzZXMgYSBkaW1lbnNpb24gdGhhdCBjYW5ub3QgZGVzY3JpYmUgYSBmcmFtZS5cbiAqXG4gKiBAcGFyYW0gbmFtZSAtIFRoZSBwYXJhbWV0ZXIgYmVpbmcgY2hlY2tlZCwgc28gdGhlIG1lc3NhZ2UgbmFtZXMgaXQuXG4gKiBAcGFyYW0gdmFsdWUgLSBXaGF0IHdhcyBwYXNzZWQuXG4gKiBAdGhyb3dzIEVycm9yIGlmIHRoZSB2YWx1ZSBpcyBub3QgYSBwb3NpdGl2ZSBmaW5pdGUgbnVtYmVyLlxuICovXG5mdW5jdGlvbiBhc3NlcnRQb3NpdGl2ZShuYW1lOiBzdHJpbmcsIHZhbHVlOiBudW1iZXIpOiB2b2lkIHtcbiAgaWYgKCFOdW1iZXIuaXNGaW5pdGUodmFsdWUpIHx8IHZhbHVlIDw9IDApIHtcbiAgICB0aHJvdyBuZXcgRXJyb3IoYHJlc29sdmVTdGF0dXNCYXJCYW5kOiAke25hbWV9IG11c3QgYmUgYSBwb3NpdGl2ZSBudW1iZXIsIGdvdCAke1N0cmluZyh2YWx1ZSl9LmApO1xuICB9XG59XG5cbi8qKlxuICogUmVuZGVycyBhIGNvbG9yIGFzIHRleHQsIGZvciBjb21wYXJpc29uIGFuZCBmb3IgdGhlIGRpYWdub3N0aWMuXG4gKlxuICogVGhlIHNhbWUgcmVuZGVyaW5nIGRvZXMgYm90aCBqb2JzIG9uIHB1cnBvc2U6IHdoYXQgYSByZWFkZXIgaXMgc2hvd24gaXMgdGhlXG4gKiB2YWx1ZSB0aGUgY29tcGFyaXNvbiB3YXMgbWFkZSBvbiwgcmF0aGVyIHRoYW4gYSByZS1mb3JtYXR0aW5nIG9mIGl0LlxuICpcbiAqIEBwYXJhbSBjb2xvciAtIFRoZSBjb2xvciwgb3IgYG51bGxgIHdoZW4gdGhlcmUgd2FzIG5vbmUgdG8gcmVwb3J0LlxuICogQHJldHVybnMgVGhlIGNvbG9yIGFzIGByZ2IocixnLGIpYCwgb3IgYChub25lKWAuXG4gKi9cbmZ1bmN0aW9uIGZvcm1hdENvbG9yKGNvbG9yOiBudWxsIHwgU3RhdHVzQmFyQmFuZENvbG9yKTogc3RyaW5nIHtcbiAgcmV0dXJuIGNvbG9yID09PSBudWxsID8gJyhub25lKScgOiBgcmdiKCR7U3RyaW5nKGNvbG9yLnIpfSwke1N0cmluZyhjb2xvci5nKX0sJHtTdHJpbmcoY29sb3IuYil9KWA7XG59XG5cbi8qKlxuICogUmVhZHMgb25lIHBpeGVsJ3MgY29sb3Igb3V0IG9mIGEgcmF3IGZyYW1lLCBpZ25vcmluZyBhbnkgYWxwaGEgY2hhbm5lbC5cbiAqXG4gKiBAcGFyYW0gcGl4ZWxzIC0gVGhlIGZyYW1lJ3MgcmF3IGJ5dGVzLlxuICogQHBhcmFtIG9mZnNldCAtIFRoZSBieXRlIG9mZnNldCBvZiB0aGUgcGl4ZWwuXG4gKiBAcmV0dXJucyBUaGUgY29sb3IgYXQgdGhhdCBvZmZzZXQuXG4gKi9cbmZ1bmN0aW9uIHJlYWRDb2xvcihwaXhlbHM6IFVpbnQ4QXJyYXksIG9mZnNldDogbnVtYmVyKTogU3RhdHVzQmFyQmFuZENvbG9yIHtcbiAgLyogdjggaWdub3JlIHN0YXJ0IC0tIFRoZSBmYWxsYmFja3MgYXJlIHVucmVhY2hhYmxlOiBldmVyeSByZWFkIGlzIGluc2lkZSBhIHJvdyBgcmVzb2x2ZVN0YXR1c0JhckJhbmRgIGhhcyBhbHJlYWR5IHByb3ZlZCB0aGUgYnVmZmVyIGxvbmcgZW5vdWdoIHRvIGhvbGQuIFRoZXkgYXJlIGhlcmUgYmVjYXVzZSBhbiBpbmRleCByZWFkIGlzIHR5cGVkIGBudW1iZXIgfCB1bmRlZmluZWRgLCBub3QgYmVjYXVzZSBhIHBpeGVsIGNhbiBiZSBtaXNzaW5nLiAqL1xuICByZXR1cm4ge1xuICAgIGI6IHBpeGVsc1tvZmZzZXQgKyBCTFVFX0NIQU5ORUxfT0ZGU0VUXSA/PyAwLFxuICAgIGc6IHBpeGVsc1tvZmZzZXQgKyBHUkVFTl9DSEFOTkVMX09GRlNFVF0gPz8gMCxcbiAgICByOiBwaXhlbHNbb2Zmc2V0ICsgUkVEX0NIQU5ORUxfT0ZGU0VUXSA/PyAwXG4gIH07XG4gIC8qIHY4IGlnbm9yZSBzdG9wICovXG59XG4iXSwKICAibWFwcGluZ3MiOiAiQUEyQ0EsU0FBUyxtQkFBbUI7QUE2S3JCLE1BQU0sMkNBQTJDO0FBUWpELE1BQU0sNENBQTRDO0FBS3pELE1BQU0sc0JBQXNCO0FBSzVCLE1BQU0scUJBQXFCO0FBSzNCLE1BQU0sdUJBQXVCO0FBUTdCLE1BQU0sK0JBQStCO0FBS3JDLE1BQU0scUJBQXFCO0FBV3BCLFNBQVMsaUNBQWlDLFFBQXdEO0FBQ3ZHLFFBQU07QUFBQSxJQUNKO0FBQUEsSUFDQTtBQUFBLElBQ0E7QUFBQSxJQUNBO0FBQUEsRUFDRixJQUFJO0FBRUosUUFBTSxRQUFRO0FBQUEsSUFDWiwyRkFDSyxPQUFPLGNBQWMsQ0FBQztBQUFBLElBQzNCLFFBQVEsT0FBTyxjQUFjLENBQUMsaUJBQWlCLE9BQU8sUUFBUSxrQkFBa0IsQ0FBQyxhQUNuRSxPQUFPLGtCQUFrQixDQUFDLEtBQUssT0FBTyxnQkFBZ0Isa0JBQWtCLENBQUMsT0FDL0UsT0FBTyxhQUFhLENBQUMscUJBQXFCLE9BQU8sUUFBUSxrQkFBa0IsQ0FBQztBQUFBLElBQ3BGLFdBQVcsWUFBWSxRQUFRLGVBQWUsQ0FBQyxXQUFXLE9BQU8sUUFBUSw2QkFBNkIsQ0FBQyxPQUMvRixZQUFZLFFBQVEsbUJBQW1CLENBQUM7QUFBQSxJQUNoRDtBQUFBLEVBR0Y7QUFFQSxTQUFPLE1BQU0sS0FBSyxJQUFJO0FBQ3hCO0FBZUEsZUFBc0Isa0JBQWtCLE9BQW1CLFNBQXdEO0FBQ2pILFFBQU0sRUFBRSxlQUFlLElBQUk7QUFDM0IsUUFBTSxxQkFBcUIsUUFBUSxzQkFBc0I7QUFDekQsUUFBTSxRQUFRLE1BQU0sWUFBWSxtQkFBbUI7QUFDbkQsUUFBTSxFQUFFLE1BQU0sS0FBSyxJQUFJLE1BQU0sTUFBTSxLQUFLLEVBQ3JDLFlBQVksRUFDWixJQUFJLEVBQ0osU0FBUyxFQUFFLG1CQUFtQixLQUFLLENBQUM7QUFFdkMs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
|