obsidian-integration-testing 10.4.0 → 12.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.
Files changed (104) hide show
  1. package/README.md +1 -1
  2. package/dist/lib/cjs/appium-server-marker.cjs +115 -0
  3. package/dist/lib/cjs/appium-server-marker.d.cts +81 -0
  4. package/dist/lib/cjs/cli.cjs +104 -19
  5. package/dist/lib/cjs/cli.d.cts +16 -8
  6. package/dist/lib/cjs/community-plugin-registry.cjs +52 -0
  7. package/dist/lib/cjs/community-plugin-registry.d.cts +116 -0
  8. package/dist/lib/cjs/context-provider.cjs +12 -1
  9. package/dist/lib/cjs/context-provider.d.cts +26 -3
  10. package/dist/lib/cjs/demo-vault-bootstrap.cjs +148 -0
  11. package/dist/lib/cjs/demo-vault-bootstrap.d.cts +113 -0
  12. package/dist/lib/cjs/demo-vault-populate.cjs +36 -9
  13. package/dist/lib/cjs/demo-vault-populate.d.cts +70 -0
  14. package/dist/lib/cjs/eval-in-obsidian.cjs +1 -1
  15. package/dist/lib/cjs/eval-in-obsidian.d.cts +97 -15
  16. package/dist/lib/cjs/index.cjs +16 -1
  17. package/dist/lib/cjs/index.d.cts +15 -8
  18. package/dist/lib/cjs/installer-asset.cjs +1 -1
  19. package/dist/lib/cjs/installer-asset.d.cts +1 -1
  20. package/dist/lib/cjs/integration-setup-failed-error.cjs +62 -0
  21. package/dist/lib/cjs/integration-setup-failed-error.d.cts +66 -0
  22. package/dist/lib/cjs/jest/global-setup.cjs +2 -2
  23. package/dist/lib/cjs/jest/global-setup.d.cts +5 -1
  24. package/dist/lib/cjs/kill-process-tree.cjs +18 -7
  25. package/dist/lib/cjs/kill-process-tree.d.cts +10 -2
  26. package/dist/lib/cjs/library.cjs +1 -1
  27. package/dist/lib/cjs/mobile-input.cjs +161 -0
  28. package/dist/lib/cjs/mobile-input.d.cts +160 -0
  29. package/dist/lib/cjs/namespace-bootstrap.cjs +136 -10
  30. package/dist/lib/cjs/obsidian-installer.cjs +1 -1
  31. package/dist/lib/cjs/obsidian-metadata.cjs +1257 -348
  32. package/dist/lib/cjs/obsidian-metadata.d.cts +74 -12
  33. package/dist/lib/cjs/obsidian-version-switch.cjs +3 -3
  34. package/dist/lib/cjs/open-obsidian-settings-tab.cjs +51 -0
  35. package/dist/lib/cjs/open-obsidian-settings-tab.d.cts +49 -0
  36. package/dist/lib/cjs/release-catalog.cjs +73 -0
  37. package/dist/lib/cjs/release-catalog.d.cts +125 -0
  38. package/dist/lib/cjs/transport-appium.cjs +122 -1
  39. package/dist/lib/cjs/transport-appium.d.cts +51 -0
  40. package/dist/lib/cjs/transport-desktop-cdp.cjs +7 -1
  41. package/dist/lib/cjs/transport-desktop-cdp.d.cts +2 -2
  42. package/dist/lib/cjs/transport-factory.cjs +237 -32
  43. package/dist/lib/cjs/transport-factory.d.cts +10 -1
  44. package/dist/lib/cjs/vitest/global-setup.cjs +13 -7
  45. package/dist/lib/cjs/vitest/global-setup.d.cts +5 -1
  46. package/dist/lib/cjs/vitest/setup.cjs +2 -1
  47. package/dist/lib/cjs/vitest/setup.d.cts +5 -0
  48. package/dist/lib/cjs/vitest/typings.d.cts +6 -4
  49. package/dist/lib/cjs/webview-cdp.cjs +264 -0
  50. package/dist/lib/cjs/webview-cdp.d.cts +152 -0
  51. package/dist/lib/cjs/wedged-appium-server.cjs +113 -0
  52. package/dist/lib/cjs/wedged-appium-server.d.cts +187 -0
  53. package/dist/lib/esm/appium-server-marker.d.mts +81 -0
  54. package/dist/lib/esm/appium-server-marker.mjs +83 -0
  55. package/dist/lib/esm/cli.d.mts +16 -8
  56. package/dist/lib/esm/cli.mjs +110 -19
  57. package/dist/lib/esm/community-plugin-registry.d.mts +116 -0
  58. package/dist/lib/esm/community-plugin-registry.mjs +23 -0
  59. package/dist/lib/esm/context-provider.d.mts +26 -3
  60. package/dist/lib/esm/context-provider.mjs +10 -1
  61. package/dist/lib/esm/demo-vault-bootstrap.d.mts +113 -0
  62. package/dist/lib/esm/demo-vault-bootstrap.mjs +127 -0
  63. package/dist/lib/esm/demo-vault-populate.d.mts +70 -0
  64. package/dist/lib/esm/demo-vault-populate.mjs +33 -8
  65. package/dist/lib/esm/eval-in-obsidian.d.mts +97 -15
  66. package/dist/lib/esm/eval-in-obsidian.mjs +1 -1
  67. package/dist/lib/esm/index.d.mts +15 -8
  68. package/dist/lib/esm/index.mjs +18 -2
  69. package/dist/lib/esm/installer-asset.d.mts +1 -1
  70. package/dist/lib/esm/installer-asset.mjs +1 -1
  71. package/dist/lib/esm/integration-setup-failed-error.d.mts +66 -0
  72. package/dist/lib/esm/integration-setup-failed-error.mjs +38 -0
  73. package/dist/lib/esm/jest/global-setup.d.mts +5 -1
  74. package/dist/lib/esm/jest/global-setup.mjs +2 -2
  75. package/dist/lib/esm/kill-process-tree.d.mts +10 -2
  76. package/dist/lib/esm/kill-process-tree.mjs +16 -6
  77. package/dist/lib/esm/library.mjs +1 -1
  78. package/dist/lib/esm/mobile-input.d.mts +160 -0
  79. package/dist/lib/esm/mobile-input.mjs +132 -0
  80. package/dist/lib/esm/namespace-bootstrap.mjs +139 -10
  81. package/dist/lib/esm/obsidian-installer.mjs +1 -1
  82. package/dist/lib/esm/obsidian-metadata.d.mts +74 -12
  83. package/dist/lib/esm/obsidian-metadata.mjs +1257 -348
  84. package/dist/lib/esm/obsidian-version-switch.mjs +3 -3
  85. package/dist/lib/esm/open-obsidian-settings-tab.d.mts +49 -0
  86. package/dist/lib/esm/open-obsidian-settings-tab.mjs +27 -0
  87. package/dist/lib/esm/release-catalog.d.mts +125 -0
  88. package/dist/lib/esm/release-catalog.mjs +46 -0
  89. package/dist/lib/esm/transport-appium.d.mts +51 -0
  90. package/dist/lib/esm/transport-appium.mjs +126 -1
  91. package/dist/lib/esm/transport-desktop-cdp.d.mts +2 -2
  92. package/dist/lib/esm/transport-desktop-cdp.mjs +7 -1
  93. package/dist/lib/esm/transport-factory.d.mts +10 -1
  94. package/dist/lib/esm/transport-factory.mjs +249 -35
  95. package/dist/lib/esm/vitest/global-setup.d.mts +5 -1
  96. package/dist/lib/esm/vitest/global-setup.mjs +14 -7
  97. package/dist/lib/esm/vitest/setup.d.mts +5 -0
  98. package/dist/lib/esm/vitest/setup.mjs +3 -1
  99. package/dist/lib/esm/vitest/typings.d.mts +6 -4
  100. package/dist/lib/esm/webview-cdp.d.mts +152 -0
  101. package/dist/lib/esm/webview-cdp.mjs +236 -0
  102. package/dist/lib/esm/wedged-appium-server.d.mts +187 -0
  103. package/dist/lib/esm/wedged-appium-server.mjs +86 -0
  104. package/package.json +13 -12
@@ -33,8 +33,12 @@ export interface CreateSetupOptions {
33
33
  * Returns files/folders to write into the vault before Obsidian opens it (see
34
34
  * {@link CoreSetupParams.populate}). A thunk so large fixtures are built lazily,
35
35
  * once, in the setup process.
36
+ *
37
+ * May return a promise, so the map can be built by something that needs the network — notably
38
+ * `buildDemoVaultPopulateAsync`, which installs a demo vault's missing community plugins before
39
+ * reading them. A synchronous thunk (e.g. `buildDemoVaultPopulate`) is unchanged.
36
40
  */
37
- populate?(this: void): PopulateFilesParams;
41
+ populate?(this: void): PopulateFilesParams | Promise<PopulateFilesParams>;
38
42
  }
39
43
  /**
40
44
  * A Vitest `globalSetup` module's `setup` / `teardown` pair.
@@ -1,5 +1,6 @@
1
1
  import { inject } from "vitest";
2
2
  import {
3
+ setSetupErrorResolver,
3
4
  setTransportOptionsResolver,
4
5
  setVaultPathResolver
5
6
  } from "../context-provider.mjs";
@@ -8,8 +9,10 @@ import {
8
9
  coreSetup,
9
10
  coreTeardown
10
11
  } from "../global-setup-core.mjs";
12
+ import { IntegrationSetupFailedError } from "../integration-setup-failed-error.mjs";
11
13
  import { log } from "../log.mjs";
12
14
  import { TemporaryVault } from "../temporary-vault.mjs";
15
+ setSetupErrorResolver(() => inject("setupError"));
13
16
  setTransportOptionsResolver(() => inject("obsidianTransport"));
14
17
  setVaultPathResolver(() => inject("temporaryVaultPath"));
15
18
  function createSetup(options) {
@@ -23,12 +26,16 @@ function createSetup(options) {
23
26
  setupResult = await coreSetup({
24
27
  enableCommunityPlugins: options?.enableCommunityPlugins,
25
28
  installPlugin: options?.installPlugin,
26
- populate: options?.populate?.(),
29
+ populate: await options?.populate?.(),
27
30
  transportOptions
28
31
  });
29
32
  } catch (error) {
30
- log(`[integration-setup:${label}] Setup failed (tests for this project will be skipped): ${errorToString(error)}`);
31
- project.provide("setupError", errorToString(error));
33
+ log(`[integration-setup:${label}] Setup failed (every test in this project will fail with this error): ${errorToString(error)}`);
34
+ project.provide("setupError", {
35
+ errorName: error instanceof Error ? error.name : "Error",
36
+ message: errorToString(error),
37
+ transportLabel: label
38
+ });
32
39
  return;
33
40
  }
34
41
  project.provide("obsidianTransport", setupResult.transportOptions);
@@ -40,9 +47,9 @@ function createSetup(options) {
40
47
  }
41
48
  function getTemporaryVault() {
42
49
  const temporaryVaultPath = inject("temporaryVaultPath");
43
- const setupErrorMessage = inject("setupError");
44
- if (setupErrorMessage) {
45
- throw new Error(`Integration setup failed \u2014 cannot get temp vault: ${setupErrorMessage}`);
50
+ const setupError = inject("setupError");
51
+ if (setupError) {
52
+ throw new IntegrationSetupFailedError(setupError);
46
53
  }
47
54
  return new TemporaryVault(temporaryVaultPath);
48
55
  }
@@ -55,4 +62,4 @@ export {
55
62
  setup,
56
63
  teardown
57
64
  };
58
- //# sourceMappingURL=data:application/json;base64,ewogICJ2ZXJzaW9uIjogMywKICAic291cmNlcyI6IFsiLi4vLi4vLi4vLi4vc3JjL3ZpdGVzdC9nbG9iYWwtc2V0dXAudHMiXSwKICAic291cmNlc0NvbnRlbnQiOiBbIi8qKlxuICogQGZpbGVcbiAqXG4gKiBWaXRlc3QgZ2xvYmFsIHNldHVwIGFuZCB0ZWFyZG93biBhZGFwdGVyLlxuICpcbiAqIERlbGVnYXRlcyB0byB0aGUgZnJhbWV3b3JrLWFnbm9zdGljIGNvcmUgYW5kIGJyaWRnZXMgY29udGV4dFxuICogdG8gdGVzdCB3b3JrZXJzIHZpYSBWaXRlc3QncyBgcHJvamVjdC5wcm92aWRlYCAvIGBpbmplY3RgLlxuICovXG5cbi8qIHY4IGlnbm9yZSBzdGFydCAtLSBJbnRlZ3JhdGlvbi10aW1lIHNldHVwIGNvdmVyZWQgYnkgaW50ZWdyYXRpb24gdGVzdHMsIG5vdCB1bml0IHRlc3RzLiAqL1xuXG5pbXBvcnQgdHlwZSB7IFRlc3RQcm9qZWN0IH0gZnJvbSAndml0ZXN0L25vZGUnO1xuXG5pbXBvcnQgeyBpbmplY3QgfSBmcm9tICd2aXRlc3QnO1xuXG5pbXBvcnQgdHlwZSB7IENvcmVTZXR1cFJlc3VsdCB9IGZyb20gJy4uL2dsb2JhbC1zZXR1cC1jb3JlLm1qcyc7XG5pbXBvcnQgdHlwZSB7IFBvcHVsYXRlRmlsZXNQYXJhbXMgfSBmcm9tICcuLi90ZW1wb3JhcnktdmF1bHQubWpzJztcbmltcG9ydCB0eXBlIHsgT2JzaWRpYW5UcmFuc3BvcnRPcHRpb25zIH0gZnJvbSAnLi4vdHJhbnNwb3J0LW9wdGlvbnMubWpzJztcblxuaW1wb3J0IHtcbiAgc2V0VHJhbnNwb3J0T3B0aW9uc1Jlc29sdmVyLFxuICBzZXRWYXVsdFBhdGhSZXNvbHZlclxufSBmcm9tICcuLi9jb250ZXh0LXByb3ZpZGVyLm1qcyc7XG5pbXBvcnQgeyBlcnJvclRvU3RyaW5nIH0gZnJvbSAnLi4vZXJyb3ItdG8tc3RyaW5nLm1qcyc7XG5pbXBvcnQge1xuICBjb3JlU2V0dXAsXG4gIGNvcmVUZWFyZG93blxufSBmcm9tICcuLi9nbG9iYWwtc2V0dXAtY29yZS5tanMnO1xuaW1wb3J0IHsgbG9nIH0gZnJvbSAnLi4vbG9nLm1qcyc7XG5pbXBvcnQgeyBUZW1wb3JhcnlWYXVsdCB9IGZyb20gJy4uL3RlbXBvcmFyeS12YXVsdC5tanMnO1xuXG5zZXRUcmFuc3BvcnRPcHRpb25zUmVzb2x2ZXIoKCkgPT4gaW5qZWN0KCdvYnNpZGlhblRyYW5zcG9ydCcpKTtcbnNldFZhdWx0UGF0aFJlc29sdmVyKCgpID0+IGluamVjdCgndGVtcG9yYXJ5VmF1bHRQYXRoJykpO1xuXG4vKipcbiAqIE9wdGlvbnMgZm9yIHtAbGluayBjcmVhdGVTZXR1cH0uXG4gKi9cbmV4cG9ydCBpbnRlcmZhY2UgQ3JlYXRlU2V0dXBPcHRpb25zIHtcbiAgLyoqXG4gICAqIENvbW11bml0eS1wbHVnaW4gaWRzIHRvIGVuYWJsZSBpbiB0aGUgdmF1bHQgaW4gYWRkaXRpb24gdG8gdGhlIHBsdWdpbi11bmRlci10ZXN0LFxuICAgKiBhZnRlciBpdCBpcyBlbmFibGVkIChzZWUge0BsaW5rIENvcmVTZXR1cFBhcmFtcy5lbmFibGVDb21tdW5pdHlQbHVnaW5zfSkuIFNlZWQgZWFjaFxuICAgKiBwbHVnaW4ncyBidWlsdCBmaWxlcyB2aWEge0BsaW5rIENyZWF0ZVNldHVwT3B0aW9ucy5wb3B1bGF0ZX0gKGUuZy4gd2l0aCBgYnVpbGREZW1vVmF1bHRQb3B1bGF0ZWApXG4gICAqIHNvIHRoZSBlbmFibGUgZmluZHMgdGhlbSBvbiBkaXNrLlxuICAgKi9cbiAgcmVhZG9ubHkgZW5hYmxlQ29tbXVuaXR5UGx1Z2lucz86IHJlYWRvbmx5IHN0cmluZ1tdO1xuXG4gIC8qKlxuICAgKiBXaGV0aGVyIHRvIGluc3RhbGwgYW5kIGVuYWJsZSB0aGUgYnVpbHQgcGx1Z2luIGluIHRoZSB0ZW1wIHZhdWx0LiBEZWZhdWx0c1xuICAgKiB0byBgdHJ1ZWAuIFNldCB0byBgZmFsc2VgIGZvciBhICoqbm9uLXBsdWdpbioqIGNvbnN1bWVyIHRoYXQgb25seSBuZWVkcyBhXG4gICAqIHJlZ2lzdGVyZWQsIGVtcHR5IHZhdWx0IHRvIGBldmFsSW5PYnNpZGlhbmAgYWdhaW5zdCBcdTIwMTQgdGhlIG93bmVkIGluc3RhbmNlIGlzXG4gICAqIHN0aWxsIGxhdW5jaGVkIGFuZCBpdHMgZW5kcG9pbnQgcHVibGlzaGVkIHRvIHdvcmtlcnMsIHNvIHJlLWV4cG9ydGluZ1xuICAgKiBgY3JlYXRlU2V0dXAoeyBpbnN0YWxsUGx1Z2luOiBmYWxzZSB9KWAgcmV1c2VzIHRoZSBzYW1lIGF0dGFjaCB3aXJpbmcgd2l0aCBub1xuICAgKiBwbHVnaW4gY29weS9lbmFibGUuIFNlZSB7QGxpbmsgQ29yZVNldHVwUGFyYW1zLmluc3RhbGxQbHVnaW59LlxuICAgKi9cbiAgcmVhZG9ubHkgaW5zdGFsbFBsdWdpbj86IGJvb2xlYW47XG5cbiAgLyoqXG4gICAqIFJldHVybnMgZmlsZXMvZm9sZGVycyB0byB3cml0ZSBpbnRvIHRoZSB2YXVsdCBiZWZvcmUgT2JzaWRpYW4gb3BlbnMgaXQgKHNlZVxuICAgKiB7QGxpbmsgQ29yZVNldHVwUGFyYW1zLnBvcHVsYXRlfSkuIEEgdGh1bmsgc28gbGFyZ2UgZml4dHVyZXMgYXJlIGJ1aWx0IGxhemlseSxcbiAgICogb25jZSwgaW4gdGhlIHNldHVwIHByb2Nlc3MuXG4gICAqL1xuICBwb3B1bGF0ZT8odGhpczogdm9pZCk6IFBvcHVsYXRlRmlsZXNQYXJhbXM7XG59XG5cbi8qKlxuICogQSBWaXRlc3QgYGdsb2JhbFNldHVwYCBtb2R1bGUncyBgc2V0dXBgIC8gYHRlYXJkb3duYCBwYWlyLlxuICovXG5leHBvcnQgaW50ZXJmYWNlIFZpdGVzdEdsb2JhbFNldHVwIHtcbiAgc2V0dXAodGhpczogdm9pZCwgcHJvamVjdDogVGVzdFByb2plY3QpOiBQcm9taXNlPHZvaWQ+O1xuICB0ZWFyZG93bih0aGlzOiB2b2lkKTogUHJvbWlzZTx2b2lkPjtcbn1cblxuLyoqXG4gKiBDcmVhdGVzIGEgVml0ZXN0IGdsb2JhbCBzZXR1cC90ZWFyZG93biBwYWlyLCBvcHRpb25hbGx5IHByZS1wb3B1bGF0aW5nIHRoZSB2YXVsdFxuICogYmVmb3JlIE9ic2lkaWFuIG9wZW5zIGl0IFx1MjAxNCB1c2UgdGhpcyBmb3IgYSBkZWRpY2F0ZWQgbGFyZ2UtdmF1bHQvcGVyZm9ybWFuY2VcbiAqIHByb2plY3QuIFRoZSBwbGFpbiB7QGxpbmsgc2V0dXB9IC8ge0BsaW5rIHRlYXJkb3dufSBleHBvcnRzIGFyZSB0aGUgbm8tcG9wdWxhdGVcbiAqIGNhc2UgKGBjcmVhdGVTZXR1cCgpYCkuIFBhc3MgYHsgaW5zdGFsbFBsdWdpbjogZmFsc2UgfWAgZm9yIGEgbm9uLXBsdWdpbiBjb25zdW1lclxuICogdGhhdCBvbmx5IG5lZWRzIGEgcmVnaXN0ZXJlZCwgZW1wdHkgdmF1bHQgKHNlZSB7QGxpbmsgQ3JlYXRlU2V0dXBPcHRpb25zLmluc3RhbGxQbHVnaW59KS5cbiAqXG4gKiBAcGFyYW0gb3B0aW9ucyAtIFNldHVwIG9wdGlvbnMuXG4gKiBAcmV0dXJucyBUaGUgYHNldHVwYCBhbmQgYHRlYXJkb3duYCBmdW5jdGlvbnMgdG8gcmUtZXhwb3J0IGZyb20gYSBgZ2xvYmFsU2V0dXBgIG1vZHVsZS5cbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIGNyZWF0ZVNldHVwKG9wdGlvbnM/OiBDcmVhdGVTZXR1cE9wdGlvbnMpOiBWaXRlc3RHbG9iYWxTZXR1cCB7XG4gIGxldCBzZXR1cFJlc3VsdDogQ29yZVNldHVwUmVzdWx0IHwgdW5kZWZpbmVkO1xuXG4gIHJldHVybiB7IHNldHVwLCB0ZWFyZG93biB9O1xuXG4gIGFzeW5jIGZ1bmN0aW9uIHNldHVwKHByb2plY3Q6IFRlc3RQcm9qZWN0KTogUHJvbWlzZTx2b2lkPiB7XG4gICAgY29uc3QgZW52aXJvbm1lbnRPcHRpb25zID0gcHJvamVjdC5jb25maWcuZW52aXJvbm1lbnRPcHRpb25zIGFzIFJlY29yZDxzdHJpbmcsIHVua25vd24+IHwgdW5kZWZpbmVkO1xuICAgIGNvbnN0IHRyYW5zcG9ydE9wdGlvbnMgPSBlbnZpcm9ubWVudE9wdGlvbnM/Llsnb2JzaWRpYW5UcmFuc3BvcnQnXSBhcyBPYnNpZGlhblRyYW5zcG9ydE9wdGlvbnMgfCB1bmRlZmluZWQ7XG4gICAgY29uc3QgbGFiZWwgPSB0cmFuc3BvcnRPcHRpb25zPy50eXBlID8/ICdvYnNpZGlhbi1jZHAnO1xuXG4gICAgdHJ5IHtcbiAgICAgIHNldHVwUmVzdWx0ID0gYXdhaXQgY29yZVNldHVwKHtcbiAgICAgICAgZW5hYmxlQ29tbXVuaXR5UGx1Z2luczogb3B0aW9ucz8uZW5hYmxlQ29tbXVuaXR5UGx1Z2lucyxcbiAgICAgICAgaW5zdGFsbFBsdWdpbjogb3B0aW9ucz8uaW5zdGFsbFBsdWdpbixcbiAgICAgICAgcG9wdWxhdGU6IG9wdGlvbnM/LnBvcHVsYXRlPy4oKSxcbiAgICAgICAgdHJhbnNwb3J0T3B0aW9uc1xuICAgICAgfSk7XG4gICAgfSBjYXRjaCAoZXJyb3I6IHVua25vd24pIHtcbiAgICAgIC8vIENhdGNoIHNldHVwIGVycm9ycyBzbyB0aGF0IG90aGVyIHByb2plY3RzJyB0ZXN0cyBjYW4gc3RpbGwgcnVuLlxuICAgICAgLy8gSW5kaXZpZHVhbCB0ZXN0cyBpbiB0aGlzIHByb2plY3Qgd2lsbCBmYWlsIHdpdGggdGhlIHN0b3JlZCBlcnJvclxuICAgICAgLy8gV2hlbiB0aGV5IHRyeSB0byBpbmplY3QgdGhlIHRlbXAgdmF1bHQgcGF0aC5cbiAgICAgIGxvZyhgW2ludGVncmF0aW9uLXNldHVwOiR7bGFiZWx9XSBTZXR1cCBmYWlsZWQgKHRlc3RzIGZvciB0aGlzIHByb2plY3Qgd2lsbCBiZSBza2lwcGVkKTogJHtlcnJvclRvU3RyaW5nKGVycm9yKX1gKTtcbiAgICAgIHByb2plY3QucHJvdmlkZSgnc2V0dXBFcnJvcicsIGVycm9yVG9TdHJpbmcoZXJyb3IpKTtcbiAgICAgIHJldHVybjtcbiAgICB9XG5cbiAgICBwcm9qZWN0LnByb3ZpZGUoJ29ic2lkaWFuVHJhbnNwb3J0Jywgc2V0dXBSZXN1bHQudHJhbnNwb3J0T3B0aW9ucyk7XG4gICAgcHJvamVjdC5wcm92aWRlKCd0ZW1wb3JhcnlWYXVsdFBhdGgnLCBzZXR1cFJlc3VsdC50ZW1wb3JhcnlWYXVsdC5wYXRoKTtcbiAgfVxuXG4gIGFzeW5jIGZ1bmN0aW9uIHRlYXJkb3duKCk6IFByb21pc2U8dm9pZD4ge1xuICAgIGF3YWl0IGNvcmVUZWFyZG93bihzZXR1cFJlc3VsdCk7XG4gIH1cbn1cblxuLyoqXG4gKiBSZXR1cm5zIHRoZSB0ZW1wb3JhcnkgdmF1bHQgcHJvdmlkZWQgYnkgdGhlIGdsb2JhbCBzZXR1cC5cbiAqXG4gKiBAcmV0dXJucyBUaGUgdGVtcG9yYXJ5IHZhdWx0LlxuICovXG5leHBvcnQgZnVuY3Rpb24gZ2V0VGVtcG9yYXJ5VmF1bHQoKTogVGVtcG9yYXJ5VmF1bHQge1xuICBjb25zdCB0ZW1wb3JhcnlWYXVsdFBhdGggPSBpbmplY3QoJ3RlbXBvcmFyeVZhdWx0UGF0aCcpO1xuICBjb25zdCBzZXR1cEVycm9yTWVzc2FnZSA9IGluamVjdCgnc2V0dXBFcnJvcicpO1xuICBpZiAoc2V0dXBFcnJvck1lc3NhZ2UpIHtcbiAgICB0aHJvdyBuZXcgRXJyb3IoYEludGVncmF0aW9uIHNldHVwIGZhaWxlZCBcdTIwMTQgY2Fubm90IGdldCB0ZW1wIHZhdWx0OiAke3NldHVwRXJyb3JNZXNzYWdlfWApO1xuICB9XG4gIHJldHVybiBuZXcgVGVtcG9yYXJ5VmF1bHQodGVtcG9yYXJ5VmF1bHRQYXRoKTtcbn1cblxuY29uc3QgZGVmYXVsdEdsb2JhbFNldHVwID0gY3JlYXRlU2V0dXAoKTtcblxuLyoqXG4gKiBWaXRlc3QgZ2xvYmFsIHNldHVwIGZ1bmN0aW9uIChubyBwcmUtcG9wdWxhdGlvbikuXG4gKlxuICogQ29waWVzIHRoZSBidWlsdCBwbHVnaW4gaW50byBhIHRlbXBvcmFyeSB2YXVsdCwgZW5hYmxlcyBpdCB2aWEgYSByZW5kZXJlciBldmFsXG4gKiBvdmVyIHRoZSB0cmFuc3BvcnQsIGFuZCBwcm92aWRlcyBgdGVtcG9yYXJ5VmF1bHRQYXRoYCB0byB0ZXN0cy5cbiAqXG4gKiBAcGFyYW0gcHJvamVjdCAtIFRoZSBWaXRlc3QgcHJvamVjdC5cbiAqIEByZXR1cm5zIEEgcHJvbWlzZSB0aGF0IHJlc29sdmVzIHdoZW4gc2V0dXAgY29tcGxldGVzLlxuICovXG5leHBvcnQgY29uc3Qgc2V0dXAgPSBkZWZhdWx0R2xvYmFsU2V0dXAuc2V0dXA7XG5cbi8qKlxuICogVml0ZXN0IGdsb2JhbCB0ZWFyZG93biBmdW5jdGlvbi5cbiAqXG4gKiBSZW1vdmVzIHRoZSB0ZW1wb3JhcnkgdmF1bHQgY3JlYXRlZCBkdXJpbmcgc2V0dXAuXG4gKlxuICogQHJldHVybnMgQSBwcm9taXNlIHRoYXQgcmVzb2x2ZXMgd2hlbiB0ZWFyZG93biBjb21wbGV0ZXMuXG4gKi9cbmV4cG9ydCBjb25zdCB0ZWFyZG93biA9IGRlZmF1bHRHbG9iYWxTZXR1cC50ZWFyZG93bjtcbiJdLAogICJtYXBwaW5ncyI6ICJBQWFBLFNBQVMsY0FBYztBQU12QjtBQUFBLEVBQ0U7QUFBQSxFQUNBO0FBQUEsT0FDSztBQUNQLFNBQVMscUJBQXFCO0FBQzlCO0FBQUEsRUFDRTtBQUFBLEVBQ0E7QUFBQSxPQUNLO0FBQ1AsU0FBUyxXQUFXO0FBQ3BCLFNBQVMsc0JBQXNCO0FBRS9CLDRCQUE0QixNQUFNLE9BQU8sbUJBQW1CLENBQUM7QUFDN0QscUJBQXFCLE1BQU0sT0FBTyxvQkFBb0IsQ0FBQztBQWtEaEQsU0FBUyxZQUFZLFNBQWlEO0FBQzNFLE1BQUk7QUFFSixTQUFPLEVBQUUsT0FBQUEsUUFBTyxVQUFBQyxVQUFTO0FBRXpCLGlCQUFlRCxPQUFNLFNBQXFDO0FBQ3hELFVBQU0scUJBQXFCLFFBQVEsT0FBTztBQUMxQyxVQUFNLG1CQUFtQixxQkFBcUIsbUJBQW1CO0FBQ2pFLFVBQU0sUUFBUSxrQkFBa0IsUUFBUTtBQUV4QyxRQUFJO0FBQ0Ysb0JBQWMsTUFBTSxVQUFVO0FBQUEsUUFDNUIsd0JBQXdCLFNBQVM7QUFBQSxRQUNqQyxlQUFlLFNBQVM7QUFBQSxRQUN4QixVQUFVLFNBQVMsV0FBVztBQUFBLFFBQzlCO0FBQUEsTUFDRixDQUFDO0FBQUEsSUFDSCxTQUFTLE9BQWdCO0FBSXZCLFVBQUksc0JBQXNCLEtBQUssNERBQTRELGNBQWMsS0FBSyxDQUFDLEVBQUU7QUFDakgsY0FBUSxRQUFRLGNBQWMsY0FBYyxLQUFLLENBQUM7QUFDbEQ7QUFBQSxJQUNGO0FBRUEsWUFBUSxRQUFRLHFCQUFxQixZQUFZLGdCQUFnQjtBQUNqRSxZQUFRLFFBQVEsc0JBQXNCLFlBQVksZUFBZSxJQUFJO0FBQUEsRUFDdkU7QUFFQSxpQkFBZUMsWUFBMEI7QUFDdkMsVUFBTSxhQUFhLFdBQVc7QUFBQSxFQUNoQztBQUNGO0FBT08sU0FBUyxvQkFBb0M7QUFDbEQsUUFBTSxxQkFBcUIsT0FBTyxvQkFBb0I7QUFDdEQsUUFBTSxvQkFBb0IsT0FBTyxZQUFZO0FBQzdDLE1BQUksbUJBQW1CO0FBQ3JCLFVBQU0sSUFBSSxNQUFNLDBEQUFxRCxpQkFBaUIsRUFBRTtBQUFBLEVBQzFGO0FBQ0EsU0FBTyxJQUFJLGVBQWUsa0JBQWtCO0FBQzlDO0FBRUEsTUFBTSxxQkFBcUIsWUFBWTtBQVdoQyxNQUFNLFFBQVEsbUJBQW1CO0FBU2pDLE1BQU0sV0FBVyxtQkFBbUI7IiwKICAibmFtZXMiOiBbInNldHVwIiwgInRlYXJkb3duIl0KfQo=
65
+ //# sourceMappingURL=data:application/json;base64,ewogICJ2ZXJzaW9uIjogMywKICAic291cmNlcyI6IFsiLi4vLi4vLi4vLi4vc3JjL3ZpdGVzdC9nbG9iYWwtc2V0dXAudHMiXSwKICAic291cmNlc0NvbnRlbnQiOiBbIi8qKlxuICogQGZpbGVcbiAqXG4gKiBWaXRlc3QgZ2xvYmFsIHNldHVwIGFuZCB0ZWFyZG93biBhZGFwdGVyLlxuICpcbiAqIERlbGVnYXRlcyB0byB0aGUgZnJhbWV3b3JrLWFnbm9zdGljIGNvcmUgYW5kIGJyaWRnZXMgY29udGV4dFxuICogdG8gdGVzdCB3b3JrZXJzIHZpYSBWaXRlc3QncyBgcHJvamVjdC5wcm92aWRlYCAvIGBpbmplY3RgLlxuICovXG5cbi8qIHY4IGlnbm9yZSBzdGFydCAtLSBJbnRlZ3JhdGlvbi10aW1lIHNldHVwIGNvdmVyZWQgYnkgaW50ZWdyYXRpb24gdGVzdHMsIG5vdCB1bml0IHRlc3RzLiAqL1xuXG5pbXBvcnQgdHlwZSB7IFRlc3RQcm9qZWN0IH0gZnJvbSAndml0ZXN0L25vZGUnO1xuXG5pbXBvcnQgeyBpbmplY3QgfSBmcm9tICd2aXRlc3QnO1xuXG5pbXBvcnQgdHlwZSB7IENvcmVTZXR1cFJlc3VsdCB9IGZyb20gJy4uL2dsb2JhbC1zZXR1cC1jb3JlLm1qcyc7XG5pbXBvcnQgdHlwZSB7IFBvcHVsYXRlRmlsZXNQYXJhbXMgfSBmcm9tICcuLi90ZW1wb3JhcnktdmF1bHQubWpzJztcbmltcG9ydCB0eXBlIHsgT2JzaWRpYW5UcmFuc3BvcnRPcHRpb25zIH0gZnJvbSAnLi4vdHJhbnNwb3J0LW9wdGlvbnMubWpzJztcblxuaW1wb3J0IHtcbiAgc2V0U2V0dXBFcnJvclJlc29sdmVyLFxuICBzZXRUcmFuc3BvcnRPcHRpb25zUmVzb2x2ZXIsXG4gIHNldFZhdWx0UGF0aFJlc29sdmVyXG59IGZyb20gJy4uL2NvbnRleHQtcHJvdmlkZXIubWpzJztcbmltcG9ydCB7IGVycm9yVG9TdHJpbmcgfSBmcm9tICcuLi9lcnJvci10by1zdHJpbmcubWpzJztcbmltcG9ydCB7XG4gIGNvcmVTZXR1cCxcbiAgY29yZVRlYXJkb3duXG59IGZyb20gJy4uL2dsb2JhbC1zZXR1cC1jb3JlLm1qcyc7XG5pbXBvcnQgeyBJbnRlZ3JhdGlvblNldHVwRmFpbGVkRXJyb3IgfSBmcm9tICcuLi9pbnRlZ3JhdGlvbi1zZXR1cC1mYWlsZWQtZXJyb3IubWpzJztcbmltcG9ydCB7IGxvZyB9IGZyb20gJy4uL2xvZy5tanMnO1xuaW1wb3J0IHsgVGVtcG9yYXJ5VmF1bHQgfSBmcm9tICcuLi90ZW1wb3JhcnktdmF1bHQubWpzJztcblxuc2V0U2V0dXBFcnJvclJlc29sdmVyKCgpID0+IGluamVjdCgnc2V0dXBFcnJvcicpKTtcbnNldFRyYW5zcG9ydE9wdGlvbnNSZXNvbHZlcigoKSA9PiBpbmplY3QoJ29ic2lkaWFuVHJhbnNwb3J0JykpO1xuc2V0VmF1bHRQYXRoUmVzb2x2ZXIoKCkgPT4gaW5qZWN0KCd0ZW1wb3JhcnlWYXVsdFBhdGgnKSk7XG5cbi8qKlxuICogT3B0aW9ucyBmb3Ige0BsaW5rIGNyZWF0ZVNldHVwfS5cbiAqL1xuZXhwb3J0IGludGVyZmFjZSBDcmVhdGVTZXR1cE9wdGlvbnMge1xuICAvKipcbiAgICogQ29tbXVuaXR5LXBsdWdpbiBpZHMgdG8gZW5hYmxlIGluIHRoZSB2YXVsdCBpbiBhZGRpdGlvbiB0byB0aGUgcGx1Z2luLXVuZGVyLXRlc3QsXG4gICAqIGFmdGVyIGl0IGlzIGVuYWJsZWQgKHNlZSB7QGxpbmsgQ29yZVNldHVwUGFyYW1zLmVuYWJsZUNvbW11bml0eVBsdWdpbnN9KS4gU2VlZCBlYWNoXG4gICAqIHBsdWdpbidzIGJ1aWx0IGZpbGVzIHZpYSB7QGxpbmsgQ3JlYXRlU2V0dXBPcHRpb25zLnBvcHVsYXRlfSAoZS5nLiB3aXRoIGBidWlsZERlbW9WYXVsdFBvcHVsYXRlYClcbiAgICogc28gdGhlIGVuYWJsZSBmaW5kcyB0aGVtIG9uIGRpc2suXG4gICAqL1xuICByZWFkb25seSBlbmFibGVDb21tdW5pdHlQbHVnaW5zPzogcmVhZG9ubHkgc3RyaW5nW107XG5cbiAgLyoqXG4gICAqIFdoZXRoZXIgdG8gaW5zdGFsbCBhbmQgZW5hYmxlIHRoZSBidWlsdCBwbHVnaW4gaW4gdGhlIHRlbXAgdmF1bHQuIERlZmF1bHRzXG4gICAqIHRvIGB0cnVlYC4gU2V0IHRvIGBmYWxzZWAgZm9yIGEgKipub24tcGx1Z2luKiogY29uc3VtZXIgdGhhdCBvbmx5IG5lZWRzIGFcbiAgICogcmVnaXN0ZXJlZCwgZW1wdHkgdmF1bHQgdG8gYGV2YWxJbk9ic2lkaWFuYCBhZ2FpbnN0IFx1MjAxNCB0aGUgb3duZWQgaW5zdGFuY2UgaXNcbiAgICogc3RpbGwgbGF1bmNoZWQgYW5kIGl0cyBlbmRwb2ludCBwdWJsaXNoZWQgdG8gd29ya2Vycywgc28gcmUtZXhwb3J0aW5nXG4gICAqIGBjcmVhdGVTZXR1cCh7IGluc3RhbGxQbHVnaW46IGZhbHNlIH0pYCByZXVzZXMgdGhlIHNhbWUgYXR0YWNoIHdpcmluZyB3aXRoIG5vXG4gICAqIHBsdWdpbiBjb3B5L2VuYWJsZS4gU2VlIHtAbGluayBDb3JlU2V0dXBQYXJhbXMuaW5zdGFsbFBsdWdpbn0uXG4gICAqL1xuICByZWFkb25seSBpbnN0YWxsUGx1Z2luPzogYm9vbGVhbjtcblxuICAvKipcbiAgICogUmV0dXJucyBmaWxlcy9mb2xkZXJzIHRvIHdyaXRlIGludG8gdGhlIHZhdWx0IGJlZm9yZSBPYnNpZGlhbiBvcGVucyBpdCAoc2VlXG4gICAqIHtAbGluayBDb3JlU2V0dXBQYXJhbXMucG9wdWxhdGV9KS4gQSB0aHVuayBzbyBsYXJnZSBmaXh0dXJlcyBhcmUgYnVpbHQgbGF6aWx5LFxuICAgKiBvbmNlLCBpbiB0aGUgc2V0dXAgcHJvY2Vzcy5cbiAgICpcbiAgICogTWF5IHJldHVybiBhIHByb21pc2UsIHNvIHRoZSBtYXAgY2FuIGJlIGJ1aWx0IGJ5IHNvbWV0aGluZyB0aGF0IG5lZWRzIHRoZSBuZXR3b3JrIFx1MjAxNCBub3RhYmx5XG4gICAqIGBidWlsZERlbW9WYXVsdFBvcHVsYXRlQXN5bmNgLCB3aGljaCBpbnN0YWxscyBhIGRlbW8gdmF1bHQncyBtaXNzaW5nIGNvbW11bml0eSBwbHVnaW5zIGJlZm9yZVxuICAgKiByZWFkaW5nIHRoZW0uIEEgc3luY2hyb25vdXMgdGh1bmsgKGUuZy4gYGJ1aWxkRGVtb1ZhdWx0UG9wdWxhdGVgKSBpcyB1bmNoYW5nZWQuXG4gICAqL1xuICBwb3B1bGF0ZT8odGhpczogdm9pZCk6IFBvcHVsYXRlRmlsZXNQYXJhbXMgfCBQcm9taXNlPFBvcHVsYXRlRmlsZXNQYXJhbXM+O1xufVxuXG4vKipcbiAqIEEgVml0ZXN0IGBnbG9iYWxTZXR1cGAgbW9kdWxlJ3MgYHNldHVwYCAvIGB0ZWFyZG93bmAgcGFpci5cbiAqL1xuZXhwb3J0IGludGVyZmFjZSBWaXRlc3RHbG9iYWxTZXR1cCB7XG4gIHNldHVwKHRoaXM6IHZvaWQsIHByb2plY3Q6IFRlc3RQcm9qZWN0KTogUHJvbWlzZTx2b2lkPjtcbiAgdGVhcmRvd24odGhpczogdm9pZCk6IFByb21pc2U8dm9pZD47XG59XG5cbi8qKlxuICogQ3JlYXRlcyBhIFZpdGVzdCBnbG9iYWwgc2V0dXAvdGVhcmRvd24gcGFpciwgb3B0aW9uYWxseSBwcmUtcG9wdWxhdGluZyB0aGUgdmF1bHRcbiAqIGJlZm9yZSBPYnNpZGlhbiBvcGVucyBpdCBcdTIwMTQgdXNlIHRoaXMgZm9yIGEgZGVkaWNhdGVkIGxhcmdlLXZhdWx0L3BlcmZvcm1hbmNlXG4gKiBwcm9qZWN0LiBUaGUgcGxhaW4ge0BsaW5rIHNldHVwfSAvIHtAbGluayB0ZWFyZG93bn0gZXhwb3J0cyBhcmUgdGhlIG5vLXBvcHVsYXRlXG4gKiBjYXNlIChgY3JlYXRlU2V0dXAoKWApLiBQYXNzIGB7IGluc3RhbGxQbHVnaW46IGZhbHNlIH1gIGZvciBhIG5vbi1wbHVnaW4gY29uc3VtZXJcbiAqIHRoYXQgb25seSBuZWVkcyBhIHJlZ2lzdGVyZWQsIGVtcHR5IHZhdWx0IChzZWUge0BsaW5rIENyZWF0ZVNldHVwT3B0aW9ucy5pbnN0YWxsUGx1Z2lufSkuXG4gKlxuICogQHBhcmFtIG9wdGlvbnMgLSBTZXR1cCBvcHRpb25zLlxuICogQHJldHVybnMgVGhlIGBzZXR1cGAgYW5kIGB0ZWFyZG93bmAgZnVuY3Rpb25zIHRvIHJlLWV4cG9ydCBmcm9tIGEgYGdsb2JhbFNldHVwYCBtb2R1bGUuXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBjcmVhdGVTZXR1cChvcHRpb25zPzogQ3JlYXRlU2V0dXBPcHRpb25zKTogVml0ZXN0R2xvYmFsU2V0dXAge1xuICBsZXQgc2V0dXBSZXN1bHQ6IENvcmVTZXR1cFJlc3VsdCB8IHVuZGVmaW5lZDtcblxuICByZXR1cm4geyBzZXR1cCwgdGVhcmRvd24gfTtcblxuICBhc3luYyBmdW5jdGlvbiBzZXR1cChwcm9qZWN0OiBUZXN0UHJvamVjdCk6IFByb21pc2U8dm9pZD4ge1xuICAgIGNvbnN0IGVudmlyb25tZW50T3B0aW9ucyA9IHByb2plY3QuY29uZmlnLmVudmlyb25tZW50T3B0aW9ucyBhcyBSZWNvcmQ8c3RyaW5nLCB1bmtub3duPiB8IHVuZGVmaW5lZDtcbiAgICBjb25zdCB0cmFuc3BvcnRPcHRpb25zID0gZW52aXJvbm1lbnRPcHRpb25zPy5bJ29ic2lkaWFuVHJhbnNwb3J0J10gYXMgT2JzaWRpYW5UcmFuc3BvcnRPcHRpb25zIHwgdW5kZWZpbmVkO1xuICAgIGNvbnN0IGxhYmVsID0gdHJhbnNwb3J0T3B0aW9ucz8udHlwZSA/PyAnb2JzaWRpYW4tY2RwJztcblxuICAgIHRyeSB7XG4gICAgICBzZXR1cFJlc3VsdCA9IGF3YWl0IGNvcmVTZXR1cCh7XG4gICAgICAgIGVuYWJsZUNvbW11bml0eVBsdWdpbnM6IG9wdGlvbnM/LmVuYWJsZUNvbW11bml0eVBsdWdpbnMsXG4gICAgICAgIGluc3RhbGxQbHVnaW46IG9wdGlvbnM/Lmluc3RhbGxQbHVnaW4sXG4gICAgICAgIHBvcHVsYXRlOiBhd2FpdCBvcHRpb25zPy5wb3B1bGF0ZT8uKCksXG4gICAgICAgIHRyYW5zcG9ydE9wdGlvbnNcbiAgICAgIH0pO1xuICAgIH0gY2F0Y2ggKGVycm9yOiB1bmtub3duKSB7XG4gICAgICAvLyBDYXRjaCBzZXR1cCBlcnJvcnMgc28gdGhhdCBvdGhlciBwcm9qZWN0cycgdGVzdHMgY2FuIHN0aWxsIHJ1bi4gRXZlcnkgdGVzdCBpbiBUSElTIHByb2plY3RcbiAgICAgIC8vIFRoZW4gZmFpbHMgd2l0aCB0aGUgc3RvcmVkIGVycm9yOiBgZ2V0T3JDcmVhdGVUcmFuc3BvcnRgIHJlYWRzIGl0IHRocm91Z2ggdGhlIHNldHVwLWVycm9yXG4gICAgICAvLyBSZXNvbHZlciBhbmQgdGhyb3dzIGJlZm9yZSBpdCBjYW4gYnVpbGQgYSB0cmFuc3BvcnQuIFdpdGhvdXQgdGhhdCBhIHdvcmtlciBnZXRzIG5vIG9wdGlvbnNcbiAgICAgIC8vIEF0IGFsbCBhbmQgc2lsZW50bHkgcmVidWlsZHMgdGhlIG93bmVkIERFU0tUT1AgZGVmYXVsdCwgd2hhdGV2ZXIgcGxhdGZvcm0gdGhpcyBwcm9qZWN0IGFza2VkXG4gICAgICAvLyBGb3IgLS0gd2hpY2ggaXMgaG93IGFuIEFuZHJvaWQgcnVuIGNhbWUgdG8gcmVwb3J0IG5pbmUgYEZhaWxlZCB0byBwYXJzZSBVUkwgZnJvbSAvanNvbmBcbiAgICAgIC8vIEZhaWx1cmVzIHdoaWxlIHRoZSByZWFsIGNhdXNlIHNhdCBvbmNlLCBmYXIgYWJvdmUsIGluIHRoaXMgbG9nIChzZWUgQUdFTlRTLm1kIEw5KS5cbiAgICAgIGxvZyhgW2ludGVncmF0aW9uLXNldHVwOiR7bGFiZWx9XSBTZXR1cCBmYWlsZWQgKGV2ZXJ5IHRlc3QgaW4gdGhpcyBwcm9qZWN0IHdpbGwgZmFpbCB3aXRoIHRoaXMgZXJyb3IpOiAke2Vycm9yVG9TdHJpbmcoZXJyb3IpfWApO1xuICAgICAgcHJvamVjdC5wcm92aWRlKCdzZXR1cEVycm9yJywge1xuICAgICAgICBlcnJvck5hbWU6IGVycm9yIGluc3RhbmNlb2YgRXJyb3IgPyBlcnJvci5uYW1lIDogJ0Vycm9yJyxcbiAgICAgICAgbWVzc2FnZTogZXJyb3JUb1N0cmluZyhlcnJvciksXG4gICAgICAgIHRyYW5zcG9ydExhYmVsOiBsYWJlbFxuICAgICAgfSk7XG4gICAgICByZXR1cm47XG4gICAgfVxuXG4gICAgcHJvamVjdC5wcm92aWRlKCdvYnNpZGlhblRyYW5zcG9ydCcsIHNldHVwUmVzdWx0LnRyYW5zcG9ydE9wdGlvbnMpO1xuICAgIHByb2plY3QucHJvdmlkZSgndGVtcG9yYXJ5VmF1bHRQYXRoJywgc2V0dXBSZXN1bHQudGVtcG9yYXJ5VmF1bHQucGF0aCk7XG4gIH1cblxuICBhc3luYyBmdW5jdGlvbiB0ZWFyZG93bigpOiBQcm9taXNlPHZvaWQ+IHtcbiAgICBhd2FpdCBjb3JlVGVhcmRvd24oc2V0dXBSZXN1bHQpO1xuICB9XG59XG5cbi8qKlxuICogUmV0dXJucyB0aGUgdGVtcG9yYXJ5IHZhdWx0IHByb3ZpZGVkIGJ5IHRoZSBnbG9iYWwgc2V0dXAuXG4gKlxuICogQHJldHVybnMgVGhlIHRlbXBvcmFyeSB2YXVsdC5cbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIGdldFRlbXBvcmFyeVZhdWx0KCk6IFRlbXBvcmFyeVZhdWx0IHtcbiAgY29uc3QgdGVtcG9yYXJ5VmF1bHRQYXRoID0gaW5qZWN0KCd0ZW1wb3JhcnlWYXVsdFBhdGgnKTtcbiAgY29uc3Qgc2V0dXBFcnJvciA9IGluamVjdCgnc2V0dXBFcnJvcicpO1xuICBpZiAoc2V0dXBFcnJvcikge1xuICAgIHRocm93IG5ldyBJbnRlZ3JhdGlvblNldHVwRmFpbGVkRXJyb3Ioc2V0dXBFcnJvcik7XG4gIH1cbiAgcmV0dXJuIG5ldyBUZW1wb3JhcnlWYXVsdCh0ZW1wb3JhcnlWYXVsdFBhdGgpO1xufVxuXG5jb25zdCBkZWZhdWx0R2xvYmFsU2V0dXAgPSBjcmVhdGVTZXR1cCgpO1xuXG4vKipcbiAqIFZpdGVzdCBnbG9iYWwgc2V0dXAgZnVuY3Rpb24gKG5vIHByZS1wb3B1bGF0aW9uKS5cbiAqXG4gKiBDb3BpZXMgdGhlIGJ1aWx0IHBsdWdpbiBpbnRvIGEgdGVtcG9yYXJ5IHZhdWx0LCBlbmFibGVzIGl0IHZpYSBhIHJlbmRlcmVyIGV2YWxcbiAqIG92ZXIgdGhlIHRyYW5zcG9ydCwgYW5kIHByb3ZpZGVzIGB0ZW1wb3JhcnlWYXVsdFBhdGhgIHRvIHRlc3RzLlxuICpcbiAqIEBwYXJhbSBwcm9qZWN0IC0gVGhlIFZpdGVzdCBwcm9qZWN0LlxuICogQHJldHVybnMgQSBwcm9taXNlIHRoYXQgcmVzb2x2ZXMgd2hlbiBzZXR1cCBjb21wbGV0ZXMuXG4gKi9cbmV4cG9ydCBjb25zdCBzZXR1cCA9IGRlZmF1bHRHbG9iYWxTZXR1cC5zZXR1cDtcblxuLyoqXG4gKiBWaXRlc3QgZ2xvYmFsIHRlYXJkb3duIGZ1bmN0aW9uLlxuICpcbiAqIFJlbW92ZXMgdGhlIHRlbXBvcmFyeSB2YXVsdCBjcmVhdGVkIGR1cmluZyBzZXR1cC5cbiAqXG4gKiBAcmV0dXJucyBBIHByb21pc2UgdGhhdCByZXNvbHZlcyB3aGVuIHRlYXJkb3duIGNvbXBsZXRlcy5cbiAqL1xuZXhwb3J0IGNvbnN0IHRlYXJkb3duID0gZGVmYXVsdEdsb2JhbFNldHVwLnRlYXJkb3duO1xuIl0sCiAgIm1hcHBpbmdzIjogIkFBYUEsU0FBUyxjQUFjO0FBTXZCO0FBQUEsRUFDRTtBQUFBLEVBQ0E7QUFBQSxFQUNBO0FBQUEsT0FDSztBQUNQLFNBQVMscUJBQXFCO0FBQzlCO0FBQUEsRUFDRTtBQUFBLEVBQ0E7QUFBQSxPQUNLO0FBQ1AsU0FBUyxtQ0FBbUM7QUFDNUMsU0FBUyxXQUFXO0FBQ3BCLFNBQVMsc0JBQXNCO0FBRS9CLHNCQUFzQixNQUFNLE9BQU8sWUFBWSxDQUFDO0FBQ2hELDRCQUE0QixNQUFNLE9BQU8sbUJBQW1CLENBQUM7QUFDN0QscUJBQXFCLE1BQU0sT0FBTyxvQkFBb0IsQ0FBQztBQXNEaEQsU0FBUyxZQUFZLFNBQWlEO0FBQzNFLE1BQUk7QUFFSixTQUFPLEVBQUUsT0FBQUEsUUFBTyxVQUFBQyxVQUFTO0FBRXpCLGlCQUFlRCxPQUFNLFNBQXFDO0FBQ3hELFVBQU0scUJBQXFCLFFBQVEsT0FBTztBQUMxQyxVQUFNLG1CQUFtQixxQkFBcUIsbUJBQW1CO0FBQ2pFLFVBQU0sUUFBUSxrQkFBa0IsUUFBUTtBQUV4QyxRQUFJO0FBQ0Ysb0JBQWMsTUFBTSxVQUFVO0FBQUEsUUFDNUIsd0JBQXdCLFNBQVM7QUFBQSxRQUNqQyxlQUFlLFNBQVM7QUFBQSxRQUN4QixVQUFVLE1BQU0sU0FBUyxXQUFXO0FBQUEsUUFDcEM7QUFBQSxNQUNGLENBQUM7QUFBQSxJQUNILFNBQVMsT0FBZ0I7QUFPdkIsVUFBSSxzQkFBc0IsS0FBSywwRUFBMEUsY0FBYyxLQUFLLENBQUMsRUFBRTtBQUMvSCxjQUFRLFFBQVEsY0FBYztBQUFBLFFBQzVCLFdBQVcsaUJBQWlCLFFBQVEsTUFBTSxPQUFPO0FBQUEsUUFDakQsU0FBUyxjQUFjLEtBQUs7QUFBQSxRQUM1QixnQkFBZ0I7QUFBQSxNQUNsQixDQUFDO0FBQ0Q7QUFBQSxJQUNGO0FBRUEsWUFBUSxRQUFRLHFCQUFxQixZQUFZLGdCQUFnQjtBQUNqRSxZQUFRLFFBQVEsc0JBQXNCLFlBQVksZUFBZSxJQUFJO0FBQUEsRUFDdkU7QUFFQSxpQkFBZUMsWUFBMEI7QUFDdkMsVUFBTSxhQUFhLFdBQVc7QUFBQSxFQUNoQztBQUNGO0FBT08sU0FBUyxvQkFBb0M7QUFDbEQsUUFBTSxxQkFBcUIsT0FBTyxvQkFBb0I7QUFDdEQsUUFBTSxhQUFhLE9BQU8sWUFBWTtBQUN0QyxNQUFJLFlBQVk7QUFDZCxVQUFNLElBQUksNEJBQTRCLFVBQVU7QUFBQSxFQUNsRDtBQUNBLFNBQU8sSUFBSSxlQUFlLGtCQUFrQjtBQUM5QztBQUVBLE1BQU0scUJBQXFCLFlBQVk7QUFXaEMsTUFBTSxRQUFRLG1CQUFtQjtBQVNqQyxNQUFNLFdBQVcsbUJBQW1COyIsCiAgIm5hbWVzIjogWyJzZXR1cCIsICJ0ZWFyZG93biJdCn0K
@@ -10,5 +10,10 @@
10
10
  * context the global setup published via `provide` (e.g. the owned instance's
11
11
  * CDP `port`). Without it, a worker falls back to launching its own un-prepared
12
12
  * transport — which, for the owned-CDP default, has no CDP endpoint.
13
+ *
14
+ * The setup-error resolver is the same channel used in reverse: when the global setup
15
+ * FAILED it publishes nothing but the failure, and this registration is what lets
16
+ * `getOrCreateTransport` throw it instead of quietly building that same un-prepared
17
+ * desktop transport in a project configured for another platform.
13
18
  */
14
19
  export {};
@@ -1,8 +1,10 @@
1
1
  import { inject } from "vitest";
2
2
  import {
3
+ setSetupErrorResolver,
3
4
  setTransportOptionsResolver,
4
5
  setVaultPathResolver
5
6
  } from "../context-provider.mjs";
7
+ setSetupErrorResolver(() => inject("setupError"));
6
8
  setTransportOptionsResolver(() => inject("obsidianTransport"));
7
9
  setVaultPathResolver(() => inject("temporaryVaultPath"));
8
- //# sourceMappingURL=data:application/json;base64,ewogICJ2ZXJzaW9uIjogMywKICAic291cmNlcyI6IFsiLi4vLi4vLi4vLi4vc3JjL3ZpdGVzdC9zZXR1cC50cyJdLAogICJzb3VyY2VzQ29udGVudCI6IFsiLyoqXG4gKiBAZmlsZVxuICpcbiAqIFZpdGVzdCAqKnBlci13b3JrZXIqKiBzZXR1cCBmaWxlLlxuICpcbiAqIFZpdGVzdCBydW5zIGBnbG9iYWxTZXR1cGAgb25seSBpbiB0aGUgbWFpbiBwcm9jZXNzLCBzbyB0aGUgdHJhbnNwb3J0LW9wdGlvbnNcbiAqIGFuZCB2YXVsdC1wYXRoIHJlc29sdmVycyByZWdpc3RlcmVkIHRoZXJlIG5ldmVyIGV4aXN0IGluIHRoZSB0ZXN0IHdvcmtlcnMgdGhhdFxuICogYWN0dWFsbHkgY2FsbCB7QGxpbmsgZXZhbEluT2JzaWRpYW59LiBBZGQgdGhpcyBtb2R1bGUgdG8gdGhlIGludGVncmF0aW9uXG4gKiBwcm9qZWN0J3MgYHNldHVwRmlsZXNgIHNvIGVhY2ggd29ya2VyIHJlZ2lzdGVycyB0aGUgcmVzb2x2ZXJzIGFuZCByZWFkcyB0aGVcbiAqIGNvbnRleHQgdGhlIGdsb2JhbCBzZXR1cCBwdWJsaXNoZWQgdmlhIGBwcm92aWRlYCAoZS5nLiB0aGUgb3duZWQgaW5zdGFuY2Unc1xuICogQ0RQIGBwb3J0YCkuIFdpdGhvdXQgaXQsIGEgd29ya2VyIGZhbGxzIGJhY2sgdG8gbGF1bmNoaW5nIGl0cyBvd24gdW4tcHJlcGFyZWRcbiAqIHRyYW5zcG9ydCBcdTIwMTQgd2hpY2gsIGZvciB0aGUgb3duZWQtQ0RQIGRlZmF1bHQsIGhhcyBubyBDRFAgZW5kcG9pbnQuXG4gKi9cbi8qIHY4IGlnbm9yZSBzdGFydCAtLSBJbnRlZ3JhdGlvbi10aW1lIHNldHVwIGNvdmVyZWQgYnkgaW50ZWdyYXRpb24gdGVzdHMsIG5vdCB1bml0IHRlc3RzLiAqL1xuXG5pbXBvcnQgeyBpbmplY3QgfSBmcm9tICd2aXRlc3QnO1xuXG5pbXBvcnQge1xuICBzZXRUcmFuc3BvcnRPcHRpb25zUmVzb2x2ZXIsXG4gIHNldFZhdWx0UGF0aFJlc29sdmVyXG59IGZyb20gJy4uL2NvbnRleHQtcHJvdmlkZXIubWpzJztcblxuc2V0VHJhbnNwb3J0T3B0aW9uc1Jlc29sdmVyKCgpID0+IGluamVjdCgnb2JzaWRpYW5UcmFuc3BvcnQnKSk7XG5zZXRWYXVsdFBhdGhSZXNvbHZlcigoKSA9PiBpbmplY3QoJ3RlbXBvcmFyeVZhdWx0UGF0aCcpKTtcblxuLyogdjggaWdub3JlIHN0b3AgKi9cbiJdLAogICJtYXBwaW5ncyI6ICJBQWVBLFNBQVMsY0FBYztBQUV2QjtBQUFBLEVBQ0U7QUFBQSxFQUNBO0FBQUEsT0FDSztBQUVQLDRCQUE0QixNQUFNLE9BQU8sbUJBQW1CLENBQUM7QUFDN0QscUJBQXFCLE1BQU0sT0FBTyxvQkFBb0IsQ0FBQzsiLAogICJuYW1lcyI6IFtdCn0K
10
+ //# sourceMappingURL=data:application/json;base64,ewogICJ2ZXJzaW9uIjogMywKICAic291cmNlcyI6IFsiLi4vLi4vLi4vLi4vc3JjL3ZpdGVzdC9zZXR1cC50cyJdLAogICJzb3VyY2VzQ29udGVudCI6IFsiLyoqXG4gKiBAZmlsZVxuICpcbiAqIFZpdGVzdCAqKnBlci13b3JrZXIqKiBzZXR1cCBmaWxlLlxuICpcbiAqIFZpdGVzdCBydW5zIGBnbG9iYWxTZXR1cGAgb25seSBpbiB0aGUgbWFpbiBwcm9jZXNzLCBzbyB0aGUgdHJhbnNwb3J0LW9wdGlvbnNcbiAqIGFuZCB2YXVsdC1wYXRoIHJlc29sdmVycyByZWdpc3RlcmVkIHRoZXJlIG5ldmVyIGV4aXN0IGluIHRoZSB0ZXN0IHdvcmtlcnMgdGhhdFxuICogYWN0dWFsbHkgY2FsbCB7QGxpbmsgZXZhbEluT2JzaWRpYW59LiBBZGQgdGhpcyBtb2R1bGUgdG8gdGhlIGludGVncmF0aW9uXG4gKiBwcm9qZWN0J3MgYHNldHVwRmlsZXNgIHNvIGVhY2ggd29ya2VyIHJlZ2lzdGVycyB0aGUgcmVzb2x2ZXJzIGFuZCByZWFkcyB0aGVcbiAqIGNvbnRleHQgdGhlIGdsb2JhbCBzZXR1cCBwdWJsaXNoZWQgdmlhIGBwcm92aWRlYCAoZS5nLiB0aGUgb3duZWQgaW5zdGFuY2Unc1xuICogQ0RQIGBwb3J0YCkuIFdpdGhvdXQgaXQsIGEgd29ya2VyIGZhbGxzIGJhY2sgdG8gbGF1bmNoaW5nIGl0cyBvd24gdW4tcHJlcGFyZWRcbiAqIHRyYW5zcG9ydCBcdTIwMTQgd2hpY2gsIGZvciB0aGUgb3duZWQtQ0RQIGRlZmF1bHQsIGhhcyBubyBDRFAgZW5kcG9pbnQuXG4gKlxuICogVGhlIHNldHVwLWVycm9yIHJlc29sdmVyIGlzIHRoZSBzYW1lIGNoYW5uZWwgdXNlZCBpbiByZXZlcnNlOiB3aGVuIHRoZSBnbG9iYWwgc2V0dXBcbiAqIEZBSUxFRCBpdCBwdWJsaXNoZXMgbm90aGluZyBidXQgdGhlIGZhaWx1cmUsIGFuZCB0aGlzIHJlZ2lzdHJhdGlvbiBpcyB3aGF0IGxldHNcbiAqIGBnZXRPckNyZWF0ZVRyYW5zcG9ydGAgdGhyb3cgaXQgaW5zdGVhZCBvZiBxdWlldGx5IGJ1aWxkaW5nIHRoYXQgc2FtZSB1bi1wcmVwYXJlZFxuICogZGVza3RvcCB0cmFuc3BvcnQgaW4gYSBwcm9qZWN0IGNvbmZpZ3VyZWQgZm9yIGFub3RoZXIgcGxhdGZvcm0uXG4gKi9cbi8qIHY4IGlnbm9yZSBzdGFydCAtLSBJbnRlZ3JhdGlvbi10aW1lIHNldHVwIGNvdmVyZWQgYnkgaW50ZWdyYXRpb24gdGVzdHMsIG5vdCB1bml0IHRlc3RzLiAqL1xuXG5pbXBvcnQgeyBpbmplY3QgfSBmcm9tICd2aXRlc3QnO1xuXG5pbXBvcnQge1xuICBzZXRTZXR1cEVycm9yUmVzb2x2ZXIsXG4gIHNldFRyYW5zcG9ydE9wdGlvbnNSZXNvbHZlcixcbiAgc2V0VmF1bHRQYXRoUmVzb2x2ZXJcbn0gZnJvbSAnLi4vY29udGV4dC1wcm92aWRlci5tanMnO1xuXG5zZXRTZXR1cEVycm9yUmVzb2x2ZXIoKCkgPT4gaW5qZWN0KCdzZXR1cEVycm9yJykpO1xuc2V0VHJhbnNwb3J0T3B0aW9uc1Jlc29sdmVyKCgpID0+IGluamVjdCgnb2JzaWRpYW5UcmFuc3BvcnQnKSk7XG5zZXRWYXVsdFBhdGhSZXNvbHZlcigoKSA9PiBpbmplY3QoJ3RlbXBvcmFyeVZhdWx0UGF0aCcpKTtcblxuLyogdjggaWdub3JlIHN0b3AgKi9cbiJdLAogICJtYXBwaW5ncyI6ICJBQW9CQSxTQUFTLGNBQWM7QUFFdkI7QUFBQSxFQUNFO0FBQUEsRUFDQTtBQUFBLEVBQ0E7QUFBQSxPQUNLO0FBRVAsc0JBQXNCLE1BQU0sT0FBTyxZQUFZLENBQUM7QUFDaEQsNEJBQTRCLE1BQU0sT0FBTyxtQkFBbUIsQ0FBQztBQUM3RCxxQkFBcUIsTUFBTSxPQUFPLG9CQUFvQixDQUFDOyIsCiAgIm5hbWVzIjogW10KfQo=
@@ -11,6 +11,7 @@
11
11
  * and `inject('obsidianTransport')` / `inject('temporaryVaultPath')`.
12
12
  */
13
13
 
14
+ import type { IntegrationSetupFailedErrorConstructorParams } from '../integration-setup-failed-error.mjs';
14
15
  import type { ObsidianTransportOptions } from '../transport-options.mjs';
15
16
 
16
17
  // eslint-disable-next-line import-x/no-unassigned-import -- Forces module resolution so declare module augmentations merge correctly.
@@ -27,11 +28,12 @@ declare module 'vitest' {
27
28
  obsidianTransport?: ObsidianTransportOptions;
28
29
 
29
30
  /**
30
- * Serialized error message from global setup failure.
31
- * Present only when setup failed tests should check this
32
- * before attempting to use the temp vault or transport.
31
+ * The global setup's failure, serialized for the workers. Present only when
32
+ * setup failed; the per-worker setup file registers it as the setup-error
33
+ * resolver, and `getOrCreateTransport` throws it rather than let a worker
34
+ * build a transport the project never asked for.
33
35
  */
34
- setupError?: string;
36
+ setupError?: IntegrationSetupFailedErrorConstructorParams;
35
37
 
36
38
  /**
37
39
  * Path to the temporary vault created by the global setup.
@@ -0,0 +1,152 @@
1
+ /**
2
+ * @file
3
+ *
4
+ * A second, independent Chrome DevTools Protocol connection to Obsidian Mobile's WebView, used to inject
5
+ * **trusted** input on Android (see **L39**).
6
+ *
7
+ * It is deliberately separate from the Appium session rather than layered on it. `AppiumTransport.evaluate`
8
+ * runs the whole `evalInObsidian` closure inside one W3C *Execute Script*, which awaits the returned
9
+ * promise — so while a closure sits waiting on `lib.clickElement(...)`, the WebDriver session is busy and
10
+ * cannot be asked to do anything else. CDP is id-multiplexed and Chromium accepts multiple debugger
11
+ * clients, so this socket stays free exactly when the Appium one does not. That is what lets the renderer
12
+ * call back out to the host mid-closure.
13
+ *
14
+ * Reaching the WebView's debugger is a two-step: the app publishes an abstract unix socket named
15
+ * `webview_devtools_remote_<pid>`, `adb forward` maps it to a local TCP port, and the usual `/json`
16
+ * endpoint then lists the page targets.
17
+ */
18
+ import type { CdpInputCommand } from './mobile-input.mjs';
19
+ /**
20
+ * One entry of the debugger's `/json` target list.
21
+ */
22
+ export interface CdpTarget {
23
+ readonly title?: string;
24
+ readonly type?: string;
25
+ readonly url?: string;
26
+ readonly webSocketDebuggerUrl?: string;
27
+ }
28
+ /**
29
+ * Parameters for {@link connectToWebViewCdp}.
30
+ */
31
+ export interface ConnectToWebViewCdpParams {
32
+ /**
33
+ * The Android application id whose WebView to attach to, e.g. `md.obsidian`.
34
+ */
35
+ readonly appId: string;
36
+ /**
37
+ * The adb device id (`emulator-5554`, a serial) to run adb against.
38
+ */
39
+ readonly deviceId: string;
40
+ }
41
+ /**
42
+ * Handles a CDP event.
43
+ */
44
+ type CdpEventHandler = (params: Record<string, unknown>) => void;
45
+ /**
46
+ * An open CDP connection to a WebView.
47
+ *
48
+ * Commands are id-multiplexed, so a command issued from inside an event handler completes while an earlier
49
+ * `Runtime.evaluate` is still awaiting its promise — which is the whole reason this class exists.
50
+ */
51
+ export declare class WebViewCdpConnection {
52
+ private readonly webSocket;
53
+ private readonly deviceId;
54
+ private readonly port;
55
+ /**
56
+ * Whether the socket is still usable.
57
+ *
58
+ * Restarting the app tears its WebView down and takes the debugger target with it, so a cached
59
+ * connection has to be re-checked rather than assumed live.
60
+ *
61
+ * @returns `true` while the socket is open.
62
+ */
63
+ get isOpen(): boolean;
64
+ private readonly eventHandlers;
65
+ private messageId;
66
+ /**
67
+ * Wraps an already-open debugger socket, plus what is needed to tear its port forward down.
68
+ *
69
+ * @param webSocket - The open debugger socket.
70
+ * @param deviceId - The adb device id, needed to drop the port forward on dispose.
71
+ * @param port - The forwarded local port.
72
+ */
73
+ constructor(webSocket: WebSocket, deviceId: string, port: number);
74
+ /**
75
+ * Closes the socket and drops the adb port forward.
76
+ */
77
+ dispose(): Promise<void>;
78
+ /**
79
+ * Synchronous disposal, for `process.on('exit')` handlers where async work cannot run.
80
+ *
81
+ * Without this an abrupt worker exit leaves the port forward behind — the async {@link dispose} above
82
+ * never gets a turn.
83
+ */
84
+ disposeSync(): void;
85
+ /**
86
+ * Subscribes to a CDP event. One handler per method; subscribing again replaces it.
87
+ *
88
+ * @param method - The CDP event name, e.g. `Runtime.bindingCalled`.
89
+ * @param handler - Called with the event parameters.
90
+ */
91
+ on(method: string, handler: CdpEventHandler): void;
92
+ /**
93
+ * Sends a CDP command and waits for its response.
94
+ *
95
+ * @param method - The CDP method name.
96
+ * @param params - The CDP method parameters.
97
+ * @returns The command result.
98
+ */
99
+ send(method: string, params?: Record<string, unknown>): Promise<Record<string, unknown>>;
100
+ /**
101
+ * Sends an ordered command sequence, honoring each command's delay.
102
+ *
103
+ * @param commands - The commands to send, in order.
104
+ */
105
+ sendAll(commands: readonly CdpInputCommand[]): Promise<void>;
106
+ /**
107
+ * Closes the debugger socket and stops routing its events.
108
+ */
109
+ private closeSocket;
110
+ }
111
+ /**
112
+ * Opens a CDP connection to the app's WebView.
113
+ *
114
+ * @param params - Which app on which device.
115
+ * @returns The open connection. Dispose it to close the socket and drop the adb port forward.
116
+ */
117
+ export declare function connectToWebViewCdp(params: ConnectToWebViewCdpParams): Promise<WebViewCdpConnection>;
118
+ /**
119
+ * Finds a port already forwarded to this device socket, so repeated runs reuse one forward.
120
+ *
121
+ * Without this every run allocates a fresh port, and any run that exits without the async
122
+ * {@link WebViewCdpConnection.dispose} — an abrupt worker exit, where only the synchronous teardown
123
+ * path runs — leaves its forward behind forever. Reuse bounds that to a single forward per socket
124
+ * rather than one per run.
125
+ *
126
+ * @param forwardList - The output of `adb forward --list`.
127
+ * @param deviceId - The adb device id to match.
128
+ * @param socketName - The abstract socket name to match.
129
+ * @returns The already-forwarded local port, or `undefined` when there is none.
130
+ */
131
+ export declare function parseForwardedPort(forwardList: string, deviceId: string, socketName: string): number | undefined;
132
+ /**
133
+ * Picks the WebView's devtools socket name out of `/proc/net/unix`.
134
+ *
135
+ * A device can host several debuggable WebViews (other apps, a leftover process), so the app's own pid is
136
+ * what disambiguates them. When no socket matches that pid the sole remaining candidate is used — a
137
+ * WebView whose socket is named after a child process rather than the one `pidof` reported still has to be
138
+ * reachable — but two unattributable candidates is ambiguous enough to refuse.
139
+ *
140
+ * @param procNetUnix - The contents of `/proc/net/unix`.
141
+ * @param pid - The application's process id.
142
+ * @returns The socket name, e.g. `webview_devtools_remote_18056`.
143
+ */
144
+ export declare function parseWebViewDevtoolsSocketName(procNetUnix: string, pid: string): string;
145
+ /**
146
+ * Picks the Obsidian page target out of the debugger's `/json` list.
147
+ *
148
+ * @param targets - The `/json` response.
149
+ * @returns The page target to attach to.
150
+ */
151
+ export declare function selectWebViewPageTarget(targets: readonly CdpTarget[]): CdpTarget;
152
+ export {};
@@ -0,0 +1,236 @@
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, honoring each command's delay.
111
+ *
112
+ * @param commands - The commands to send, in order.
113
+ */
114
+ async sendAll(commands) {
115
+ for (const command of commands) {
116
+ if (command.delayBeforeInMilliseconds !== void 0) {
117
+ await new Promise((resolve) => {
118
+ setTimeout(resolve, command.delayBeforeInMilliseconds);
119
+ });
120
+ }
121
+ await this.send(command.method, command.params);
122
+ }
123
+ }
124
+ /**
125
+ * Closes the debugger socket and stops routing its events.
126
+ */
127
+ closeSocket() {
128
+ this.eventHandlers.clear();
129
+ if (this.webSocket.readyState === WebSocket.OPEN) {
130
+ this.webSocket.close();
131
+ }
132
+ }
133
+ }
134
+ async function connectToWebViewCdp(params) {
135
+ const { appId, deviceId } = params;
136
+ const pidofOutput = await adb(deviceId, ["shell", "pidof", appId]);
137
+ const pid = pidofOutput.split(/\s+/, 1)[0] ?? "";
138
+ if (!pid) {
139
+ throw new Error(`${appId} is not running on ${deviceId}, so its WebView has no debugger to attach to.`);
140
+ }
141
+ const procNetUnix = await adb(deviceId, ["shell", "cat", "/proc/net/unix"]);
142
+ const socketName = parseWebViewDevtoolsSocketName(procNetUnix, pid);
143
+ const forwardList = await adb(deviceId, ["forward", "--list"]);
144
+ const existingPort = parseForwardedPort(forwardList, deviceId, socketName);
145
+ const port = existingPort ?? await pickFreePort();
146
+ if (existingPort === void 0) {
147
+ await adb(deviceId, ["forward", `tcp:${String(port)}`, `localabstract:${socketName}`]);
148
+ log(`[webview-cdp] Forwarded tcp:${String(port)} -> localabstract:${socketName} (${appId} pid ${pid})`);
149
+ } else {
150
+ log(`[webview-cdp] Reusing forward tcp:${String(port)} -> localabstract:${socketName} (${appId} pid ${pid})`);
151
+ }
152
+ try {
153
+ const response = await fetch(`http://${CDP_HOST}:${String(port)}/json`);
154
+ const target = selectWebViewPageTarget(await response.json());
155
+ const webSocket = new WebSocket(target.webSocketDebuggerUrl ?? "");
156
+ await new Promise((resolve, reject) => {
157
+ webSocket.addEventListener("open", () => {
158
+ resolve();
159
+ }, { once: true });
160
+ webSocket.addEventListener("error", () => {
161
+ reject(new Error(`Could not open the WebView CDP socket at ${target.webSocketDebuggerUrl ?? ""}.`));
162
+ }, { once: true });
163
+ });
164
+ log(`[webview-cdp] Attached to WebView target "${target.title ?? ""}"`);
165
+ return new WebViewCdpConnection(webSocket, deviceId, port);
166
+ } catch (error) {
167
+ await removeForward(deviceId, port);
168
+ throw error;
169
+ }
170
+ }
171
+ function parseForwardedPort(forwardList, deviceId, socketName) {
172
+ for (const line of forwardList.split("\n")) {
173
+ const [lineDeviceId, local, remote] = line.trim().split(/\s+/, FORWARD_LIST_COLUMN_COUNT);
174
+ if (lineDeviceId !== deviceId || remote !== `localabstract:${socketName}` || !local?.startsWith("tcp:")) {
175
+ continue;
176
+ }
177
+ const port = Number.parseInt(local.slice("tcp:".length), DECIMAL_RADIX);
178
+ if (Number.isSafeInteger(port)) {
179
+ return port;
180
+ }
181
+ }
182
+ return void 0;
183
+ }
184
+ function parseWebViewDevtoolsSocketName(procNetUnix, pid) {
185
+ const names = [
186
+ ...new Set([...procNetUnix.matchAll(/@webview_devtools_remote_\w+/g)].map((match) => match[0].slice("@".length)))
187
+ ];
188
+ const [soleName, ...otherNames] = names;
189
+ if (soleName === void 0) {
190
+ throw new Error(
191
+ "No `webview_devtools_remote_*` socket on the device. Obsidian Mobile is not running, or its WebView debugger is disabled."
192
+ );
193
+ }
194
+ const exactName = `webview_devtools_remote_${pid}`;
195
+ if (names.includes(exactName)) {
196
+ return exactName;
197
+ }
198
+ if (otherNames.length === 0) {
199
+ return soleName;
200
+ }
201
+ throw new Error(
202
+ `No \`${exactName}\` socket, and ${String(names.length)} other debuggable WebViews are present (${names.join(", ")}), so the right one cannot be chosen.`
203
+ );
204
+ }
205
+ function selectWebViewPageTarget(targets) {
206
+ const target = targets.find((candidate) => candidate.type === "page" && Boolean(candidate.webSocketDebuggerUrl)) ?? targets.find((candidate) => Boolean(candidate.webSocketDebuggerUrl));
207
+ if (!target) {
208
+ throw new Error("The WebView debugger listed no target with a WebSocket endpoint.");
209
+ }
210
+ return target;
211
+ }
212
+ async function adb(deviceId, commandArguments) {
213
+ const stdout = await exec(["adb", "-s", deviceId, ...commandArguments], {
214
+ isQuiet: true,
215
+ timeoutInMilliseconds: ADB_TIMEOUT_IN_MILLISECONDS
216
+ });
217
+ return stdout.trim();
218
+ }
219
+ async function removeForward(deviceId, port) {
220
+ try {
221
+ await exec(["adb", "-s", deviceId, "forward", "--remove", `tcp:${String(port)}`], {
222
+ isQuiet: true,
223
+ shouldIgnoreExitCode: true,
224
+ timeoutInMilliseconds: ADB_TIMEOUT_IN_MILLISECONDS
225
+ });
226
+ } catch {
227
+ }
228
+ }
229
+ export {
230
+ WebViewCdpConnection,
231
+ connectToWebViewCdp,
232
+ parseForwardedPort,
233
+ parseWebViewDevtoolsSocketName,
234
+ selectWebViewPageTarget
235
+ };
236
+ //# sourceMappingURL=data:application/json;base64,ewogICJ2ZXJzaW9uIjogMywKICAic291cmNlcyI6IFsiLi4vLi4vLi4vc3JjL3dlYnZpZXctY2RwLnRzIl0sCiAgInNvdXJjZXNDb250ZW50IjogWyIvKipcbiAqIEBmaWxlXG4gKlxuICogQSBzZWNvbmQsIGluZGVwZW5kZW50IENocm9tZSBEZXZUb29scyBQcm90b2NvbCBjb25uZWN0aW9uIHRvIE9ic2lkaWFuIE1vYmlsZSdzIFdlYlZpZXcsIHVzZWQgdG8gaW5qZWN0XG4gKiAqKnRydXN0ZWQqKiBpbnB1dCBvbiBBbmRyb2lkIChzZWUgKipMMzkqKikuXG4gKlxuICogSXQgaXMgZGVsaWJlcmF0ZWx5IHNlcGFyYXRlIGZyb20gdGhlIEFwcGl1bSBzZXNzaW9uIHJhdGhlciB0aGFuIGxheWVyZWQgb24gaXQuIGBBcHBpdW1UcmFuc3BvcnQuZXZhbHVhdGVgXG4gKiBydW5zIHRoZSB3aG9sZSBgZXZhbEluT2JzaWRpYW5gIGNsb3N1cmUgaW5zaWRlIG9uZSBXM0MgKkV4ZWN1dGUgU2NyaXB0Kiwgd2hpY2ggYXdhaXRzIHRoZSByZXR1cm5lZFxuICogcHJvbWlzZSBcdTIwMTQgc28gd2hpbGUgYSBjbG9zdXJlIHNpdHMgd2FpdGluZyBvbiBgbGliLmNsaWNrRWxlbWVudCguLi4pYCwgdGhlIFdlYkRyaXZlciBzZXNzaW9uIGlzIGJ1c3kgYW5kXG4gKiBjYW5ub3QgYmUgYXNrZWQgdG8gZG8gYW55dGhpbmcgZWxzZS4gQ0RQIGlzIGlkLW11bHRpcGxleGVkIGFuZCBDaHJvbWl1bSBhY2NlcHRzIG11bHRpcGxlIGRlYnVnZ2VyXG4gKiBjbGllbnRzLCBzbyB0aGlzIHNvY2tldCBzdGF5cyBmcmVlIGV4YWN0bHkgd2hlbiB0aGUgQXBwaXVtIG9uZSBkb2VzIG5vdC4gVGhhdCBpcyB3aGF0IGxldHMgdGhlIHJlbmRlcmVyXG4gKiBjYWxsIGJhY2sgb3V0IHRvIHRoZSBob3N0IG1pZC1jbG9zdXJlLlxuICpcbiAqIFJlYWNoaW5nIHRoZSBXZWJWaWV3J3MgZGVidWdnZXIgaXMgYSB0d28tc3RlcDogdGhlIGFwcCBwdWJsaXNoZXMgYW4gYWJzdHJhY3QgdW5peCBzb2NrZXQgbmFtZWRcbiAqIGB3ZWJ2aWV3X2RldnRvb2xzX3JlbW90ZV88cGlkPmAsIGBhZGIgZm9yd2FyZGAgbWFwcyBpdCB0byBhIGxvY2FsIFRDUCBwb3J0LCBhbmQgdGhlIHVzdWFsIGAvanNvbmBcbiAqIGVuZHBvaW50IHRoZW4gbGlzdHMgdGhlIHBhZ2UgdGFyZ2V0cy5cbiAqL1xuXG5pbXBvcnQgeyBleGVjRmlsZVN5bmMgfSBmcm9tICdub2RlOmNoaWxkX3Byb2Nlc3MnO1xuXG5pbXBvcnQgdHlwZSB7IENkcElucHV0Q29tbWFuZCB9IGZyb20gJy4vbW9iaWxlLWlucHV0Lm1qcyc7XG5cbmltcG9ydCB7IGV4ZWMgfSBmcm9tICcuL2V4ZWMubWpzJztcbmltcG9ydCB7IGxvZyB9IGZyb20gJy4vbG9nLm1qcyc7XG5pbXBvcnQgeyBwaWNrRnJlZVBvcnQgfSBmcm9tICcuL29ic2lkaWFuLWluc3RhbmNlLm1qcyc7XG5cbmNvbnN0IEFEQl9USU1FT1VUX0lOX01JTExJU0VDT05EUyA9IDE1XzAwMDtcbi8vIGBhZGIgZm9yd2FyZCAtLWxpc3RgIHByaW50cyB0aHJlZSBjb2x1bW5zOiBgPGRldmljZUlkPiB0Y3A6PHBvcnQ+IGxvY2FsYWJzdHJhY3Q6PHNvY2tldE5hbWU+YC5cbmNvbnN0IEZPUldBUkRfTElTVF9DT0xVTU5fQ09VTlQgPSAzO1xuY29uc3QgREVDSU1BTF9SQURJWCA9IDEwO1xuY29uc3QgQ0RQX0NPTU1BTkRfVElNRU9VVF9JTl9NSUxMSVNFQ09ORFMgPSAzMF8wMDA7XG5jb25zdCBDRFBfSE9TVCA9ICcxMjcuMC4wLjEnO1xuXG4vKipcbiAqIE9uZSBlbnRyeSBvZiB0aGUgZGVidWdnZXIncyBgL2pzb25gIHRhcmdldCBsaXN0LlxuICovXG5leHBvcnQgaW50ZXJmYWNlIENkcFRhcmdldCB7XG4gIHJlYWRvbmx5IHRpdGxlPzogc3RyaW5nO1xuICByZWFkb25seSB0eXBlPzogc3RyaW5nO1xuICByZWFkb25seSB1cmw/OiBzdHJpbmc7XG4gIHJlYWRvbmx5IHdlYlNvY2tldERlYnVnZ2VyVXJsPzogc3RyaW5nO1xufVxuXG4vKipcbiAqIFBhcmFtZXRlcnMgZm9yIHtAbGluayBjb25uZWN0VG9XZWJWaWV3Q2RwfS5cbiAqL1xuZXhwb3J0IGludGVyZmFjZSBDb25uZWN0VG9XZWJWaWV3Q2RwUGFyYW1zIHtcbiAgLyoqXG4gICAqIFRoZSBBbmRyb2lkIGFwcGxpY2F0aW9uIGlkIHdob3NlIFdlYlZpZXcgdG8gYXR0YWNoIHRvLCBlLmcuIGBtZC5vYnNpZGlhbmAuXG4gICAqL1xuICByZWFkb25seSBhcHBJZDogc3RyaW5nO1xuXG4gIC8qKlxuICAgKiBUaGUgYWRiIGRldmljZSBpZCAoYGVtdWxhdG9yLTU1NTRgLCBhIHNlcmlhbCkgdG8gcnVuIGFkYiBhZ2FpbnN0LlxuICAgKi9cbiAgcmVhZG9ubHkgZGV2aWNlSWQ6IHN0cmluZztcbn1cblxuLyoqXG4gKiBUaGUgZXJyb3IgaGFsZiBvZiBhIGZhaWxlZCBDRFAgY29tbWFuZCByZXNwb25zZS5cbiAqL1xuaW50ZXJmYWNlIENkcEVycm9yIHtcbiAgcmVhZG9ubHkgbWVzc2FnZT86IHN0cmluZztcbn1cblxuLyoqXG4gKiBIYW5kbGVzIGEgQ0RQIGV2ZW50LlxuICovXG50eXBlIENkcEV2ZW50SGFuZGxlciA9IChwYXJhbXM6IFJlY29yZDxzdHJpbmcsIHVua25vd24+KSA9PiB2b2lkO1xuXG4vKipcbiAqIEEgQ0RQIG1lc3NhZ2U6IGVpdGhlciBhIHJlc3BvbnNlIHRvIGEgY29tbWFuZCAoYGlkYCkgb3IgYW4gZXZlbnQgKGBtZXRob2RgKS5cbiAqL1xuaW50ZXJmYWNlIENkcE1lc3NhZ2Uge1xuICByZWFkb25seSBlcnJvcj86IENkcEVycm9yO1xuICByZWFkb25seSBpZD86IG51bWJlcjtcbiAgcmVhZG9ubHkgbWV0aG9kPzogc3RyaW5nO1xuICByZWFkb25seSBwYXJhbXM/OiBSZWNvcmQ8c3RyaW5nLCB1bmtub3duPjtcbiAgcmVhZG9ubHkgcmVzdWx0PzogUmVjb3JkPHN0cmluZywgdW5rbm93bj47XG59XG5cbi8qIHY4IGlnbm9yZSBzdGFydCAtLSBJbnRlZ3JhdGlvbi10aW1lIGNvZGUgKGEgbGl2ZSBXZWJTb2NrZXQgdG8gYSBXZWJWaWV3LCBhZGIgcG9ydCBmb3J3YXJkaW5nKS4gQ292ZXJlZCBieSB0aGUgQW5kcm9pZCBpbnRlZ3JhdGlvbiB0ZXN0cywgbm90IGJ5IHVuaXQgdGVzdHMuIEl0IGVuZHMgYmVmb3JlIGBwYXJzZUZvcndhcmRlZFBvcnRgOiBgcGVyZmVjdGlvbmlzdC9zb3J0LW1vZHVsZXNgIHBsYWNlcyB0aGUgcHVyZSwgdW5pdC10ZXN0ZWQgaGVscGVycyBBRlRFUiB0aGlzIGNsYXNzIGFuZCBgY29ubmVjdFRvV2ViVmlld0NkcGAsIHNvIHRoZXkgbXVzdCBzdGF5IG1lYXN1cmVkLiAqL1xuXG4vKipcbiAqIEFuIG9wZW4gQ0RQIGNvbm5lY3Rpb24gdG8gYSBXZWJWaWV3LlxuICpcbiAqIENvbW1hbmRzIGFyZSBpZC1tdWx0aXBsZXhlZCwgc28gYSBjb21tYW5kIGlzc3VlZCBmcm9tIGluc2lkZSBhbiBldmVudCBoYW5kbGVyIGNvbXBsZXRlcyB3aGlsZSBhbiBlYXJsaWVyXG4gKiBgUnVudGltZS5ldmFsdWF0ZWAgaXMgc3RpbGwgYXdhaXRpbmcgaXRzIHByb21pc2UgXHUyMDE0IHdoaWNoIGlzIHRoZSB3aG9sZSByZWFzb24gdGhpcyBjbGFzcyBleGlzdHMuXG4gKi9cbmV4cG9ydCBjbGFzcyBXZWJWaWV3Q2RwQ29ubmVjdGlvbiB7XG4gIC8qKlxuICAgKiBXaGV0aGVyIHRoZSBzb2NrZXQgaXMgc3RpbGwgdXNhYmxlLlxuICAgKlxuICAgKiBSZXN0YXJ0aW5nIHRoZSBhcHAgdGVhcnMgaXRzIFdlYlZpZXcgZG93biBhbmQgdGFrZXMgdGhlIGRlYnVnZ2VyIHRhcmdldCB3aXRoIGl0LCBzbyBhIGNhY2hlZFxuICAgKiBjb25uZWN0aW9uIGhhcyB0byBiZSByZS1jaGVja2VkIHJhdGhlciB0aGFuIGFzc3VtZWQgbGl2ZS5cbiAgICpcbiAgICogQHJldHVybnMgYHRydWVgIHdoaWxlIHRoZSBzb2NrZXQgaXMgb3Blbi5cbiAgICovXG4gIHB1YmxpYyBnZXQgaXNPcGVuKCk6IGJvb2xlYW4ge1xuICAgIHJldHVybiB0aGlzLndlYlNvY2tldC5yZWFkeVN0YXRlID09PSBXZWJTb2NrZXQuT1BFTjtcbiAgfVxuXG4gIHByaXZhdGUgcmVhZG9ubHkgZXZlbnRIYW5kbGVycyA9IG5ldyBNYXA8c3RyaW5nLCBDZHBFdmVudEhhbmRsZXI+KCk7XG5cbiAgcHJpdmF0ZSBtZXNzYWdlSWQgPSAwO1xuXG4gIC8qKlxuICAgKiBXcmFwcyBhbiBhbHJlYWR5LW9wZW4gZGVidWdnZXIgc29ja2V0LCBwbHVzIHdoYXQgaXMgbmVlZGVkIHRvIHRlYXIgaXRzIHBvcnQgZm9yd2FyZCBkb3duLlxuICAgKlxuICAgKiBAcGFyYW0gd2ViU29ja2V0IC0gVGhlIG9wZW4gZGVidWdnZXIgc29ja2V0LlxuICAgKiBAcGFyYW0gZGV2aWNlSWQgLSBUaGUgYWRiIGRldmljZSBpZCwgbmVlZGVkIHRvIGRyb3AgdGhlIHBvcnQgZm9yd2FyZCBvbiBkaXNwb3NlLlxuICAgKiBAcGFyYW0gcG9ydCAtIFRoZSBmb3J3YXJkZWQgbG9jYWwgcG9ydC5cbiAgICovXG4gIHB1YmxpYyBjb25zdHJ1Y3Rvcihwcml2YXRlIHJlYWRvbmx5IHdlYlNvY2tldDogV2ViU29ja2V0LCBwcml2YXRlIHJlYWRvbmx5IGRldmljZUlkOiBzdHJpbmcsIHByaXZhdGUgcmVhZG9ubHkgcG9ydDogbnVtYmVyKSB7XG4gICAgdGhpcy53ZWJTb2NrZXQuYWRkRXZlbnRMaXN0ZW5lcignbWVzc2FnZScsIChldmVudDogTWVzc2FnZUV2ZW50KSA9PiB7XG4gICAgICBjb25zdCBtZXNzYWdlID0gSlNPTi5wYXJzZShTdHJpbmcoZXZlbnQuZGF0YSkpIGFzIENkcE1lc3NhZ2U7XG4gICAgICBpZiAobWVzc2FnZS5tZXRob2QgPT09IHVuZGVmaW5lZCkge1xuICAgICAgICByZXR1cm47XG4gICAgICB9XG5cbiAgICAgIHRoaXMuZXZlbnRIYW5kbGVycy5nZXQobWVzc2FnZS5tZXRob2QpPy4obWVzc2FnZS5wYXJhbXMgPz8ge30pO1xuICAgIH0pO1xuICB9XG5cbiAgLyoqXG4gICAqIENsb3NlcyB0aGUgc29ja2V0IGFuZCBkcm9wcyB0aGUgYWRiIHBvcnQgZm9yd2FyZC5cbiAgICovXG4gIHB1YmxpYyBhc3luYyBkaXNwb3NlKCk6IFByb21pc2U8dm9pZD4ge1xuICAgIHRoaXMuY2xvc2VTb2NrZXQoKTtcbiAgICBhd2FpdCByZW1vdmVGb3J3YXJkKHRoaXMuZGV2aWNlSWQsIHRoaXMucG9ydCk7XG4gIH1cblxuICAvKipcbiAgICogU3luY2hyb25vdXMgZGlzcG9zYWwsIGZvciBgcHJvY2Vzcy5vbignZXhpdCcpYCBoYW5kbGVycyB3aGVyZSBhc3luYyB3b3JrIGNhbm5vdCBydW4uXG4gICAqXG4gICAqIFdpdGhvdXQgdGhpcyBhbiBhYnJ1cHQgd29ya2VyIGV4aXQgbGVhdmVzIHRoZSBwb3J0IGZvcndhcmQgYmVoaW5kIFx1MjAxNCB0aGUgYXN5bmMge0BsaW5rIGRpc3Bvc2V9IGFib3ZlXG4gICAqIG5ldmVyIGdldHMgYSB0dXJuLlxuICAgKi9cbiAgcHVibGljIGRpc3Bvc2VTeW5jKCk6IHZvaWQge1xuICAgIHRoaXMuY2xvc2VTb2NrZXQoKTtcbiAgICB0cnkge1xuICAgICAgZXhlY0ZpbGVTeW5jKCdhZGInLCBbJy1zJywgdGhpcy5kZXZpY2VJZCwgJ2ZvcndhcmQnLCAnLS1yZW1vdmUnLCBgdGNwOiR7U3RyaW5nKHRoaXMucG9ydCl9YF0sIHtcbiAgICAgICAgc3RkaW86ICdpZ25vcmUnLFxuICAgICAgICB0aW1lb3V0OiBBREJfVElNRU9VVF9JTl9NSUxMSVNFQ09ORFNcbiAgICAgIH0pO1xuICAgIH0gY2F0Y2gge1xuICAgICAgLy8gQmVzdCBlZmZvcnQ6IGFkYiBkcm9wcyBldmVyeSBmb3J3YXJkIHdoZW4gdGhlIGRldmljZSBkaXNjb25uZWN0cyBhbnl3YXkuXG4gICAgfVxuICB9XG5cbiAgLyoqXG4gICAqIFN1YnNjcmliZXMgdG8gYSBDRFAgZXZlbnQuIE9uZSBoYW5kbGVyIHBlciBtZXRob2Q7IHN1YnNjcmliaW5nIGFnYWluIHJlcGxhY2VzIGl0LlxuICAgKlxuICAgKiBAcGFyYW0gbWV0aG9kIC0gVGhlIENEUCBldmVudCBuYW1lLCBlLmcuIGBSdW50aW1lLmJpbmRpbmdDYWxsZWRgLlxuICAgKiBAcGFyYW0gaGFuZGxlciAtIENhbGxlZCB3aXRoIHRoZSBldmVudCBwYXJhbWV0ZXJzLlxuICAgKi9cbiAgcHVibGljIG9uKG1ldGhvZDogc3RyaW5nLCBoYW5kbGVyOiBDZHBFdmVudEhhbmRsZXIpOiB2b2lkIHtcbiAgICB0aGlzLmV2ZW50SGFuZGxlcnMuc2V0KG1ldGhvZCwgaGFuZGxlcik7XG4gIH1cblxuICAvKipcbiAgICogU2VuZHMgYSBDRFAgY29tbWFuZCBhbmQgd2FpdHMgZm9yIGl0cyByZXNwb25zZS5cbiAgICpcbiAgICogQHBhcmFtIG1ldGhvZCAtIFRoZSBDRFAgbWV0aG9kIG5hbWUuXG4gICAqIEBwYXJhbSBwYXJhbXMgLSBUaGUgQ0RQIG1ldGhvZCBwYXJhbWV0ZXJzLlxuICAgKiBAcmV0dXJucyBUaGUgY29tbWFuZCByZXN1bHQuXG4gICAqL1xuICBwdWJsaWMgc2VuZChtZXRob2Q6IHN0cmluZywgcGFyYW1zOiBSZWNvcmQ8c3RyaW5nLCB1bmtub3duPiA9IHt9KTogUHJvbWlzZTxSZWNvcmQ8c3RyaW5nLCB1bmtub3duPj4ge1xuICAgIGNvbnN0IGlkID0gKyt0aGlzLm1lc3NhZ2VJZDtcblxuICAgIHJldHVybiBuZXcgUHJvbWlzZTxSZWNvcmQ8c3RyaW5nLCB1bmtub3duPj4oKHJlc29sdmUsIHJlamVjdCkgPT4ge1xuICAgICAgY29uc3QgdGltZW91dCA9IHNldFRpbWVvdXQoKCkgPT4ge1xuICAgICAgICB0aGlzLndlYlNvY2tldC5yZW1vdmVFdmVudExpc3RlbmVyKCdtZXNzYWdlJywgaGFuZGxlcik7XG4gICAgICAgIHJlamVjdChuZXcgRXJyb3IoYFdlYlZpZXcgQ0RQIGNvbW1hbmQgdGltZWQgb3V0IGFmdGVyICR7U3RyaW5nKENEUF9DT01NQU5EX1RJTUVPVVRfSU5fTUlMTElTRUNPTkRTKX1tczogJHttZXRob2R9YCkpO1xuICAgICAgfSwgQ0RQX0NPTU1BTkRfVElNRU9VVF9JTl9NSUxMSVNFQ09ORFMpO1xuXG4gICAgICBjb25zdCBoYW5kbGVyID0gKGV2ZW50OiBNZXNzYWdlRXZlbnQpOiB2b2lkID0+IHtcbiAgICAgICAgY29uc3QgbWVzc2FnZSA9IEpTT04ucGFyc2UoU3RyaW5nKGV2ZW50LmRhdGEpKSBhcyBDZHBNZXNzYWdlO1xuICAgICAgICBpZiAobWVzc2FnZS5pZCAhPT0gaWQpIHtcbiAgICAgICAgICByZXR1cm47XG4gICAgICAgIH1cblxuICAgICAgICBjbGVhclRpbWVvdXQodGltZW91dCk7XG4gICAgICAgIHRoaXMud2ViU29ja2V0LnJlbW92ZUV2ZW50TGlzdGVuZXIoJ21lc3NhZ2UnLCBoYW5kbGVyKTtcbiAgICAgICAgaWYgKG1lc3NhZ2UuZXJyb3IpIHtcbiAgICAgICAgICByZWplY3QobmV3IEVycm9yKGBXZWJWaWV3IENEUCBjb21tYW5kICR7bWV0aG9kfSBmYWlsZWQ6ICR7bWVzc2FnZS5lcnJvci5tZXNzYWdlID8/ICd1bmtub3duIGVycm9yJ31gKSk7XG4gICAgICAgICAgcmV0dXJuO1xuICAgICAgICB9XG5cbiAgICAgICAgcmVzb2x2ZShtZXNzYWdlLnJlc3VsdCA/PyB7fSk7XG4gICAgICB9O1xuXG4gICAgICB0aGlzLndlYlNvY2tldC5hZGRFdmVudExpc3RlbmVyKCdtZXNzYWdlJywgaGFuZGxlcik7XG4gICAgICB0aGlzLndlYlNvY2tldC5zZW5kKEpTT04uc3RyaW5naWZ5KHsgaWQsIG1ldGhvZCwgcGFyYW1zIH0pKTtcbiAgICB9KTtcbiAgfVxuXG4gIC8qKlxuICAgKiBTZW5kcyBhbiBvcmRlcmVkIGNvbW1hbmQgc2VxdWVuY2UsIGhvbm9yaW5nIGVhY2ggY29tbWFuZCdzIGRlbGF5LlxuICAgKlxuICAgKiBAcGFyYW0gY29tbWFuZHMgLSBUaGUgY29tbWFuZHMgdG8gc2VuZCwgaW4gb3JkZXIuXG4gICAqL1xuICBwdWJsaWMgYXN5bmMgc2VuZEFsbChjb21tYW5kczogcmVhZG9ubHkgQ2RwSW5wdXRDb21tYW5kW10pOiBQcm9taXNlPHZvaWQ+IHtcbiAgICBmb3IgKGNvbnN0IGNvbW1hbmQgb2YgY29tbWFuZHMpIHtcbiAgICAgIGlmIChjb21tYW5kLmRlbGF5QmVmb3JlSW5NaWxsaXNlY29uZHMgIT09IHVuZGVmaW5lZCkge1xuICAgICAgICBhd2FpdCBuZXcgUHJvbWlzZTx2b2lkPigocmVzb2x2ZSkgPT4ge1xuICAgICAgICAgIHNldFRpbWVvdXQocmVzb2x2ZSwgY29tbWFuZC5kZWxheUJlZm9yZUluTWlsbGlzZWNvbmRzKTtcbiAgICAgICAgfSk7XG4gICAgICB9XG5cbiAgICAgIGF3YWl0IHRoaXMuc2VuZChjb21tYW5kLm1ldGhvZCwgY29tbWFuZC5wYXJhbXMpO1xuICAgIH1cbiAgfVxuXG4gIC8qKlxuICAgKiBDbG9zZXMgdGhlIGRlYnVnZ2VyIHNvY2tldCBhbmQgc3RvcHMgcm91dGluZyBpdHMgZXZlbnRzLlxuICAgKi9cbiAgcHJpdmF0ZSBjbG9zZVNvY2tldCgpOiB2b2lkIHtcbiAgICB0aGlzLmV2ZW50SGFuZGxlcnMuY2xlYXIoKTtcbiAgICBpZiAodGhpcy53ZWJTb2NrZXQucmVhZHlTdGF0ZSA9PT0gV2ViU29ja2V0Lk9QRU4pIHtcbiAgICAgIHRoaXMud2ViU29ja2V0LmNsb3NlKCk7XG4gICAgfVxuICB9XG59XG5cbi8qKlxuICogT3BlbnMgYSBDRFAgY29ubmVjdGlvbiB0byB0aGUgYXBwJ3MgV2ViVmlldy5cbiAqXG4gKiBAcGFyYW0gcGFyYW1zIC0gV2hpY2ggYXBwIG9uIHdoaWNoIGRldmljZS5cbiAqIEByZXR1cm5zIFRoZSBvcGVuIGNvbm5lY3Rpb24uIERpc3Bvc2UgaXQgdG8gY2xvc2UgdGhlIHNvY2tldCBhbmQgZHJvcCB0aGUgYWRiIHBvcnQgZm9yd2FyZC5cbiAqL1xuZXhwb3J0IGFzeW5jIGZ1bmN0aW9uIGNvbm5lY3RUb1dlYlZpZXdDZHAocGFyYW1zOiBDb25uZWN0VG9XZWJWaWV3Q2RwUGFyYW1zKTogUHJvbWlzZTxXZWJWaWV3Q2RwQ29ubmVjdGlvbj4ge1xuICBjb25zdCB7IGFwcElkLCBkZXZpY2VJZCB9ID0gcGFyYW1zO1xuXG4gIC8vIGBwaWRvZmAgbGlzdHMgZXZlcnkgbWF0Y2hpbmcgcHJvY2VzczsgdGhlIGFwcCdzIG1haW4gb25lIGlzIGZpcnN0LlxuICBjb25zdCBwaWRvZk91dHB1dCA9IGF3YWl0IGFkYihkZXZpY2VJZCwgWydzaGVsbCcsICdwaWRvZicsIGFwcElkXSk7XG4gIGNvbnN0IHBpZCA9IHBpZG9mT3V0cHV0LnNwbGl0KC9cXHMrLywgMSlbMF0gPz8gJyc7XG4gIGlmICghcGlkKSB7XG4gICAgdGhyb3cgbmV3IEVycm9yKGAke2FwcElkfSBpcyBub3QgcnVubmluZyBvbiAke2RldmljZUlkfSwgc28gaXRzIFdlYlZpZXcgaGFzIG5vIGRlYnVnZ2VyIHRvIGF0dGFjaCB0by5gKTtcbiAgfVxuXG4gIGNvbnN0IHByb2NOZXRVbml4ID0gYXdhaXQgYWRiKGRldmljZUlkLCBbJ3NoZWxsJywgJ2NhdCcsICcvcHJvYy9uZXQvdW5peCddKTtcbiAgY29uc3Qgc29ja2V0TmFtZSA9IHBhcnNlV2ViVmlld0RldnRvb2xzU29ja2V0TmFtZShwcm9jTmV0VW5peCwgcGlkKTtcblxuICBjb25zdCBmb3J3YXJkTGlzdCA9IGF3YWl0IGFkYihkZXZpY2VJZCwgWydmb3J3YXJkJywgJy0tbGlzdCddKTtcbiAgY29uc3QgZXhpc3RpbmdQb3J0ID0gcGFyc2VGb3J3YXJkZWRQb3J0KGZvcndhcmRMaXN0LCBkZXZpY2VJZCwgc29ja2V0TmFtZSk7XG4gIGNvbnN0IHBvcnQgPSBleGlzdGluZ1BvcnQgPz8gYXdhaXQgcGlja0ZyZWVQb3J0KCk7XG4gIGlmIChleGlzdGluZ1BvcnQgPT09IHVuZGVmaW5lZCkge1xuICAgIGF3YWl0IGFkYihkZXZpY2VJZCwgWydmb3J3YXJkJywgYHRjcDoke1N0cmluZyhwb3J0KX1gLCBgbG9jYWxhYnN0cmFjdDoke3NvY2tldE5hbWV9YF0pO1xuICAgIGxvZyhgW3dlYnZpZXctY2RwXSBGb3J3YXJkZWQgdGNwOiR7U3RyaW5nKHBvcnQpfSAtPiBsb2NhbGFic3RyYWN0OiR7c29ja2V0TmFtZX0gKCR7YXBwSWR9IHBpZCAke3BpZH0pYCk7XG4gIH0gZWxzZSB7XG4gICAgbG9nKGBbd2Vidmlldy1jZHBdIFJldXNpbmcgZm9yd2FyZCB0Y3A6JHtTdHJpbmcocG9ydCl9IC0+IGxvY2FsYWJzdHJhY3Q6JHtzb2NrZXROYW1lfSAoJHthcHBJZH0gcGlkICR7cGlkfSlgKTtcbiAgfVxuXG4gIHRyeSB7XG4gICAgY29uc3QgcmVzcG9uc2UgPSBhd2FpdCBmZXRjaChgaHR0cDovLyR7Q0RQX0hPU1R9OiR7U3RyaW5nKHBvcnQpfS9qc29uYCk7XG4gICAgY29uc3QgdGFyZ2V0ID0gc2VsZWN0V2ViVmlld1BhZ2VUYXJnZXQoYXdhaXQgcmVzcG9uc2UuanNvbigpIGFzIENkcFRhcmdldFtdKTtcbiAgICBjb25zdCB3ZWJTb2NrZXQgPSBuZXcgV2ViU29ja2V0KHRhcmdldC53ZWJTb2NrZXREZWJ1Z2dlclVybCA/PyAnJyk7XG4gICAgYXdhaXQgbmV3IFByb21pc2U8dm9pZD4oKHJlc29sdmUsIHJlamVjdCkgPT4ge1xuICAgICAgd2ViU29ja2V0LmFkZEV2ZW50TGlzdGVuZXIoJ29wZW4nLCAoKSA9PiB7XG4gICAgICAgIHJlc29sdmUoKTtcbiAgICAgIH0sIHsgb25jZTogdHJ1ZSB9KTtcbiAgICAgIHdlYlNvY2tldC5hZGRFdmVudExpc3RlbmVyKCdlcnJvcicsICgpID0+IHtcbiAgICAgICAgcmVqZWN0KG5ldyBFcnJvcihgQ291bGQgbm90IG9wZW4gdGhlIFdlYlZpZXcgQ0RQIHNvY2tldCBhdCAke3RhcmdldC53ZWJTb2NrZXREZWJ1Z2dlclVybCA/PyAnJ30uYCkpO1xuICAgICAgfSwgeyBvbmNlOiB0cnVlIH0pO1xuICAgIH0pO1xuICAgIGxvZyhgW3dlYnZpZXctY2RwXSBBdHRhY2hlZCB0byBXZWJWaWV3IHRhcmdldCBcIiR7dGFyZ2V0LnRpdGxlID8/ICcnfVwiYCk7XG5cbiAgICByZXR1cm4gbmV3IFdlYlZpZXdDZHBDb25uZWN0aW9uKHdlYlNvY2tldCwgZGV2aWNlSWQsIHBvcnQpO1xuICB9IGNhdGNoIChlcnJvcjogdW5rbm93bikge1xuICAgIGF3YWl0IHJlbW92ZUZvcndhcmQoZGV2aWNlSWQsIHBvcnQpO1xuICAgIHRocm93IGVycm9yO1xuICB9XG59XG5cbi8qIHY4IGlnbm9yZSBzdG9wICovXG5cbi8qKlxuICogRmluZHMgYSBwb3J0IGFscmVhZHkgZm9yd2FyZGVkIHRvIHRoaXMgZGV2aWNlIHNvY2tldCwgc28gcmVwZWF0ZWQgcnVucyByZXVzZSBvbmUgZm9yd2FyZC5cbiAqXG4gKiBXaXRob3V0IHRoaXMgZXZlcnkgcnVuIGFsbG9jYXRlcyBhIGZyZXNoIHBvcnQsIGFuZCBhbnkgcnVuIHRoYXQgZXhpdHMgd2l0aG91dCB0aGUgYXN5bmNcbiAqIHtAbGluayBXZWJWaWV3Q2RwQ29ubmVjdGlvbi5kaXNwb3NlfSBcdTIwMTQgYW4gYWJydXB0IHdvcmtlciBleGl0LCB3aGVyZSBvbmx5IHRoZSBzeW5jaHJvbm91cyB0ZWFyZG93blxuICogcGF0aCBydW5zIFx1MjAxNCBsZWF2ZXMgaXRzIGZvcndhcmQgYmVoaW5kIGZvcmV2ZXIuIFJldXNlIGJvdW5kcyB0aGF0IHRvIGEgc2luZ2xlIGZvcndhcmQgcGVyIHNvY2tldFxuICogcmF0aGVyIHRoYW4gb25lIHBlciBydW4uXG4gKlxuICogQHBhcmFtIGZvcndhcmRMaXN0IC0gVGhlIG91dHB1dCBvZiBgYWRiIGZvcndhcmQgLS1saXN0YC5cbiAqIEBwYXJhbSBkZXZpY2VJZCAtIFRoZSBhZGIgZGV2aWNlIGlkIHRvIG1hdGNoLlxuICogQHBhcmFtIHNvY2tldE5hbWUgLSBUaGUgYWJzdHJhY3Qgc29ja2V0IG5hbWUgdG8gbWF0Y2guXG4gKiBAcmV0dXJucyBUaGUgYWxyZWFkeS1mb3J3YXJkZWQgbG9jYWwgcG9ydCwgb3IgYHVuZGVmaW5lZGAgd2hlbiB0aGVyZSBpcyBub25lLlxuICovXG5leHBvcnQgZnVuY3Rpb24gcGFyc2VGb3J3YXJkZWRQb3J0KGZvcndhcmRMaXN0OiBzdHJpbmcsIGRldmljZUlkOiBzdHJpbmcsIHNvY2tldE5hbWU6IHN0cmluZyk6IG51bWJlciB8IHVuZGVmaW5lZCB7XG4gIGZvciAoY29uc3QgbGluZSBvZiBmb3J3YXJkTGlzdC5zcGxpdCgnXFxuJykpIHtcbiAgICAvLyBgYWRiIGZvcndhcmQgLS1saXN0YCBwcmludHMgYDxkZXZpY2VJZD4gdGNwOjxwb3J0PiBsb2NhbGFic3RyYWN0Ojxzb2NrZXROYW1lPmAuXG4gICAgY29uc3QgW2xpbmVEZXZpY2VJZCwgbG9jYWwsIHJlbW90ZV0gPSBsaW5lLnRyaW0oKS5zcGxpdCgvXFxzKy8sIEZPUldBUkRfTElTVF9DT0xVTU5fQ09VTlQpO1xuICAgIGlmIChsaW5lRGV2aWNlSWQgIT09IGRldmljZUlkIHx8IHJlbW90ZSAhPT0gYGxvY2FsYWJzdHJhY3Q6JHtzb2NrZXROYW1lfWAgfHwgIWxvY2FsPy5zdGFydHNXaXRoKCd0Y3A6JykpIHtcbiAgICAgIGNvbnRpbnVlO1xuICAgIH1cblxuICAgIGNvbnN0IHBvcnQgPSBOdW1iZXIucGFyc2VJbnQobG9jYWwuc2xpY2UoJ3RjcDonLmxlbmd0aCksIERFQ0lNQUxfUkFESVgpO1xuICAgIGlmIChOdW1iZXIuaXNTYWZlSW50ZWdlcihwb3J0KSkge1xuICAgICAgcmV0dXJuIHBvcnQ7XG4gICAgfVxuICB9XG5cbiAgcmV0dXJuIHVuZGVmaW5lZDtcbn1cblxuLyoqXG4gKiBQaWNrcyB0aGUgV2ViVmlldydzIGRldnRvb2xzIHNvY2tldCBuYW1lIG91dCBvZiBgL3Byb2MvbmV0L3VuaXhgLlxuICpcbiAqIEEgZGV2aWNlIGNhbiBob3N0IHNldmVyYWwgZGVidWdnYWJsZSBXZWJWaWV3cyAob3RoZXIgYXBwcywgYSBsZWZ0b3ZlciBwcm9jZXNzKSwgc28gdGhlIGFwcCdzIG93biBwaWQgaXNcbiAqIHdoYXQgZGlzYW1iaWd1YXRlcyB0aGVtLiBXaGVuIG5vIHNvY2tldCBtYXRjaGVzIHRoYXQgcGlkIHRoZSBzb2xlIHJlbWFpbmluZyBjYW5kaWRhdGUgaXMgdXNlZCBcdTIwMTQgYVxuICogV2ViVmlldyB3aG9zZSBzb2NrZXQgaXMgbmFtZWQgYWZ0ZXIgYSBjaGlsZCBwcm9jZXNzIHJhdGhlciB0aGFuIHRoZSBvbmUgYHBpZG9mYCByZXBvcnRlZCBzdGlsbCBoYXMgdG8gYmVcbiAqIHJlYWNoYWJsZSBcdTIwMTQgYnV0IHR3byB1bmF0dHJpYnV0YWJsZSBjYW5kaWRhdGVzIGlzIGFtYmlndW91cyBlbm91Z2ggdG8gcmVmdXNlLlxuICpcbiAqIEBwYXJhbSBwcm9jTmV0VW5peCAtIFRoZSBjb250ZW50cyBvZiBgL3Byb2MvbmV0L3VuaXhgLlxuICogQHBhcmFtIHBpZCAtIFRoZSBhcHBsaWNhdGlvbidzIHByb2Nlc3MgaWQuXG4gKiBAcmV0dXJucyBUaGUgc29ja2V0IG5hbWUsIGUuZy4gYHdlYnZpZXdfZGV2dG9vbHNfcmVtb3RlXzE4MDU2YC5cbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIHBhcnNlV2ViVmlld0RldnRvb2xzU29ja2V0TmFtZShwcm9jTmV0VW5peDogc3RyaW5nLCBwaWQ6IHN0cmluZyk6IHN0cmluZyB7XG4gIC8vIFRoZSB3aG9sZSBtYXRjaCBtaW51cyBpdHMgbGVhZGluZyBgQGAsIHJhdGhlciB0aGFuIGEgbmFtZWQgY2FwdHVyZSBncm91cDogYSBtYW5kYXRvcnkgZ3JvdXAgaXMgYWx3YXlzXG4gIC8vIFByZXNlbnQgb25jZSB0aGUgcGF0dGVybiBoYXMgbWF0Y2hlZCwgc28gYG1hdGNoLmdyb3Vwcz8uWyduYW1lJ10gPz8gJydgIHdvdWxkIGxlYXZlIGJlaGluZCB0d28gYnJhbmNoZXNcbiAgLy8gTm8gaW5wdXQgY2FuIGV2ZXIgcmVhY2ggXHUyMDE0IGFuZCB0aGlzIGZpbGUgaXMgbWVhc3VyZWQgYWdhaW5zdCBhIDEwMCUgYnJhbmNoIGdhdGUuXG4gIGNvbnN0IG5hbWVzID0gW1xuICAgIC4uLm5ldyBTZXQoWy4uLnByb2NOZXRVbml4Lm1hdGNoQWxsKC9Ad2Vidmlld19kZXZ0b29sc19yZW1vdGVfXFx3Ky9nKV1cbiAgICAgIC5tYXAoKG1hdGNoKSA9PiBtYXRjaFswXS5zbGljZSgnQCcubGVuZ3RoKSkpXG4gIF07XG5cbiAgLy8gRGVzdHJ1Y3R1cmVkIHJhdGhlciB0aGFuIGluZGV4ZWQgZm9yIHRoZSBzYW1lIHJlYXNvbjogYHNvbGVOYW1lYCBjYXJyaWVzIHRoZSBcInRoZXJlIGlzIGF0IGxlYXN0IG9uZVwiXG4gIC8vIEZhY3QgaW4gdGhlIHR5cGUsIHNvIG5laXRoZXIgdGhlIGVtcHR5IGNoZWNrIGJlbG93IG5vciB0aGUgc2luZ2xlLWNhbmRpZGF0ZSBvbmUgbGVhdmVzIGEgZGVhZCBicmFuY2guXG4gIGNvbnN0IFtzb2xlTmFtZSwgLi4ub3RoZXJOYW1lc10gPSBuYW1lcztcblxuICBpZiAoc29sZU5hbWUgPT09IHVuZGVmaW5lZCkge1xuICAgIHRocm93IG5ldyBFcnJvcihcbiAgICAgICdObyBgd2Vidmlld19kZXZ0b29sc19yZW1vdGVfKmAgc29ja2V0IG9uIHRoZSBkZXZpY2UuIE9ic2lkaWFuIE1vYmlsZSBpcyBub3QgcnVubmluZywgb3IgaXRzIFdlYlZpZXcgJ1xuICAgICAgICArICdkZWJ1Z2dlciBpcyBkaXNhYmxlZC4nXG4gICAgKTtcbiAgfVxuXG4gIGNvbnN0IGV4YWN0TmFtZSA9IGB3ZWJ2aWV3X2RldnRvb2xzX3JlbW90ZV8ke3BpZH1gO1xuICBpZiAobmFtZXMuaW5jbHVkZXMoZXhhY3ROYW1lKSkge1xuICAgIHJldHVybiBleGFjdE5hbWU7XG4gIH1cblxuICBpZiAob3RoZXJOYW1lcy5sZW5ndGggPT09IDApIHtcbiAgICByZXR1cm4gc29sZU5hbWU7XG4gIH1cblxuICB0aHJvdyBuZXcgRXJyb3IoXG4gICAgYE5vIFxcYCR7ZXhhY3ROYW1lfVxcYCBzb2NrZXQsIGFuZCAke1N0cmluZyhuYW1lcy5sZW5ndGgpfSBvdGhlciBkZWJ1Z2dhYmxlIFdlYlZpZXdzIGFyZSBwcmVzZW50IGBcbiAgICAgICsgYCgke25hbWVzLmpvaW4oJywgJyl9KSwgc28gdGhlIHJpZ2h0IG9uZSBjYW5ub3QgYmUgY2hvc2VuLmBcbiAgKTtcbn1cblxuLyoqXG4gKiBQaWNrcyB0aGUgT2JzaWRpYW4gcGFnZSB0YXJnZXQgb3V0IG9mIHRoZSBkZWJ1Z2dlcidzIGAvanNvbmAgbGlzdC5cbiAqXG4gKiBAcGFyYW0gdGFyZ2V0cyAtIFRoZSBgL2pzb25gIHJlc3BvbnNlLlxuICogQHJldHVybnMgVGhlIHBhZ2UgdGFyZ2V0IHRvIGF0dGFjaCB0by5cbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIHNlbGVjdFdlYlZpZXdQYWdlVGFyZ2V0KHRhcmdldHM6IHJlYWRvbmx5IENkcFRhcmdldFtdKTogQ2RwVGFyZ2V0IHtcbiAgY29uc3QgdGFyZ2V0ID0gdGFyZ2V0cy5maW5kKChjYW5kaWRhdGUpID0+IGNhbmRpZGF0ZS50eXBlID09PSAncGFnZScgJiYgQm9vbGVhbihjYW5kaWRhdGUud2ViU29ja2V0RGVidWdnZXJVcmwpKVxuICAgID8/IHRhcmdldHMuZmluZCgoY2FuZGlkYXRlKSA9PiBCb29sZWFuKGNhbmRpZGF0ZS53ZWJTb2NrZXREZWJ1Z2dlclVybCkpO1xuXG4gIGlmICghdGFyZ2V0KSB7XG4gICAgdGhyb3cgbmV3IEVycm9yKCdUaGUgV2ViVmlldyBkZWJ1Z2dlciBsaXN0ZWQgbm8gdGFyZ2V0IHdpdGggYSBXZWJTb2NrZXQgZW5kcG9pbnQuJyk7XG4gIH1cblxuICByZXR1cm4gdGFyZ2V0O1xufVxuXG4vKiB2OCBpZ25vcmUgc3RhcnQgLS0gSW50ZWdyYXRpb24tdGltZSBjb2RlIChhZGIgc2hlbGwtb3V0cykuIENvdmVyZWQgYnkgdGhlIEFuZHJvaWQgaW50ZWdyYXRpb24gdGVzdHMsIG5vdCBieSB1bml0IHRlc3RzLiAqL1xuXG4vKipcbiAqIFJ1bnMgYW4gYWRiIGNvbW1hbmQgYWdhaW5zdCBvbmUgZGV2aWNlLlxuICpcbiAqIEBwYXJhbSBkZXZpY2VJZCAtIFRoZSBhZGIgZGV2aWNlIGlkLlxuICogQHBhcmFtIGNvbW1hbmRBcmd1bWVudHMgLSBUaGUgYWRiIGFyZ3VtZW50cyBhZnRlciBgLXMgPGRldmljZUlkPmAuXG4gKiBAcmV0dXJucyBUcmltbWVkIHN0ZG91dC5cbiAqL1xuYXN5bmMgZnVuY3Rpb24gYWRiKGRldmljZUlkOiBzdHJpbmcsIGNvbW1hbmRBcmd1bWVudHM6IHN0cmluZ1tdKTogUHJvbWlzZTxzdHJpbmc+IHtcbiAgY29uc3Qgc3Rkb3V0ID0gYXdhaXQgZXhlYyhbJ2FkYicsICctcycsIGRldmljZUlkLCAuLi5jb21tYW5kQXJndW1lbnRzXSwge1xuICAgIGlzUXVpZXQ6IHRydWUsXG4gICAgdGltZW91dEluTWlsbGlzZWNvbmRzOiBBREJfVElNRU9VVF9JTl9NSUxMSVNFQ09ORFNcbiAgfSk7XG5cbiAgcmV0dXJuIHN0ZG91dC50cmltKCk7XG59XG5cbi8qKlxuICogRHJvcHMgYW4gYWRiIHBvcnQgZm9yd2FyZCwgaWdub3JpbmcgdGhlIFwibm90IGZvdW5kXCIgY2FzZS5cbiAqXG4gKiBAcGFyYW0gZGV2aWNlSWQgLSBUaGUgYWRiIGRldmljZSBpZC5cbiAqIEBwYXJhbSBwb3J0IC0gVGhlIGZvcndhcmRlZCBsb2NhbCBwb3J0LlxuICovXG5hc3luYyBmdW5jdGlvbiByZW1vdmVGb3J3YXJkKGRldmljZUlkOiBzdHJpbmcsIHBvcnQ6IG51bWJlcik6IFByb21pc2U8dm9pZD4ge1xuICB0cnkge1xuICAgIGF3YWl0IGV4ZWMoWydhZGInLCAnLXMnLCBkZXZpY2VJZCwgJ2ZvcndhcmQnLCAnLS1yZW1vdmUnLCBgdGNwOiR7U3RyaW5nKHBvcnQpfWBdLCB7XG4gICAgICBpc1F1aWV0OiB0cnVlLFxuICAgICAgc2hvdWxkSWdub3JlRXhpdENvZGU6IHRydWUsXG4gICAgICB0aW1lb3V0SW5NaWxsaXNlY29uZHM6IEFEQl9USU1FT1VUX0lOX01JTExJU0VDT05EU1xuICAgIH0pO1xuICB9IGNhdGNoIHtcbiAgICAvLyBCZXN0IGVmZm9ydDogdGhlIGZvcndhcmQgaXMgcGVyLXJ1biBhbmQgYWRiIGRyb3BzIGl0IHdoZW4gdGhlIGRldmljZSBkaXNjb25uZWN0cyBhbnl3YXkuXG4gIH1cbn1cblxuLyogdjggaWdub3JlIHN0b3AgKi9cbiJdLAogICJtYXBwaW5ncyI6ICJBQWtCQSxTQUFTLG9CQUFvQjtBQUk3QixTQUFTLFlBQVk7QUFDckIsU0FBUyxXQUFXO0FBQ3BCLFNBQVMsb0JBQW9CO0FBRTdCLE1BQU0sOEJBQThCO0FBRXBDLE1BQU0sNEJBQTRCO0FBQ2xDLE1BQU0sZ0JBQWdCO0FBQ3RCLE1BQU0sc0NBQXNDO0FBQzVDLE1BQU0sV0FBVztBQTBEVixNQUFNLHFCQUFxQjtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUEsRUF3QnpCLFlBQTZCLFdBQXVDLFVBQW1DLE1BQWM7QUFBeEY7QUFBdUM7QUFBbUM7QUFDNUcsU0FBSyxVQUFVLGlCQUFpQixXQUFXLENBQUMsVUFBd0I7QUFDbEUsWUFBTSxVQUFVLEtBQUssTUFBTSxPQUFPLE1BQU0sSUFBSSxDQUFDO0FBQzdDLFVBQUksUUFBUSxXQUFXLFFBQVc7QUFDaEM7QUFBQSxNQUNGO0FBRUEsV0FBSyxjQUFjLElBQUksUUFBUSxNQUFNLElBQUksUUFBUSxVQUFVLENBQUMsQ0FBQztBQUFBLElBQy9ELENBQUM7QUFBQSxFQUNIO0FBQUEsRUFUb0M7QUFBQSxFQUF1QztBQUFBLEVBQW1DO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBLEVBZjlHLElBQVcsU0FBa0I7QUFDM0IsV0FBTyxLQUFLLFVBQVUsZUFBZSxVQUFVO0FBQUEsRUFDakQ7QUFBQSxFQUVpQixnQkFBZ0Isb0JBQUksSUFBNkI7QUFBQSxFQUUxRCxZQUFZO0FBQUE7QUFBQTtBQUFBO0FBQUEsRUF1QnBCLE1BQWEsVUFBeUI7QUFDcEMsU0FBSyxZQUFZO0FBQ2pCLFVBQU0sY0FBYyxLQUFLLFVBQVUsS0FBSyxJQUFJO0FBQUEsRUFD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