visualizer-on-tabs 1.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2016 cheminfo
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,113 @@
1
+ # visualizer-on-tabs
2
+
3
+ Builds a static website that has multiple instances of the visualizer that can communicate with each other.
4
+
5
+ ## CLI usage
6
+
7
+ ```bash
8
+ npx visualizer-on-tabs --config=./config.json --outDir=./out
9
+ ```
10
+
11
+ ## Configuration
12
+
13
+ Example: https://github.com/cheminfo/cheminfo-server-setup/blob/master/doc/on-tabs/config.json
14
+
15
+ ```js
16
+ const config = {
17
+ // Title of the single page app
18
+ title: 'My app',
19
+ // List of default views to load
20
+ possibleViews: {
21
+ Home: {
22
+ url: 'https://couch.cheminfo.org/cheminfo-public/158ef2f0cc85bfc5b4f2d88cff473e83/view.json',
23
+ },
24
+ },
25
+ // Rules on how visualizer view URLs should be rewritten when a tab is opened
26
+ rewriteRules: [
27
+ {
28
+ reg: '^([a-z0-9]+)\\?(.*)$',
29
+ replace: 'https://couch.cheminfo.org/cheminfo-public/$1/view.json?$2',
30
+ },
31
+ {
32
+ reg: '^[a-z0-9]+$',
33
+ replace: 'https://couch.cheminfo.org/cheminfo-public/$&/view.json',
34
+ },
35
+ {
36
+ reg: '^[a-z0-9]+/view.json\\?.*',
37
+ replace: 'https://couch.cheminfo.org/cheminfo-public/$&',
38
+ },
39
+ ],
40
+ // Setting this to true loads all the tabs (in possibleViews) on page load
41
+ // It is discouraged to do this because loading hidden iframes
42
+ // lead to layout issues. Especially in Firefox.
43
+ // When false, only the selected tab is loaded.
44
+ loadHidden: false,
45
+ // The visualizer configuration object that will be passed to each visualizer instance
46
+ visualizerConfig: undefined,
47
+ // The version of the visualizer to load. By default it 'auto', which uses
48
+ // the version stored in the loaded view.
49
+ visualizerVersion: 'auto',
50
+ // Options passed to `makeVisualizerPage`, see https://github.com/cheminfo/react-visualizer
51
+ // Respectively `fallbackVersion` and `cdn`.
52
+ visualizerFallbackVersion: undefined,
53
+ visualizerCDN: undefined,
54
+ };
55
+ ```
56
+
57
+ ## Dev setup
58
+
59
+ Here is how you can test your changes in the visualizer-on-tabs react app.
60
+
61
+ ### Build the page with a custom configuration
62
+
63
+ There is a dev configuration in `./dev.json`, which is used by local scripts to build a working visualizer-on-tabs app.
64
+
65
+ To build in dev mode with automatic rebuild, run:
66
+
67
+ ```bash
68
+ npm run build:dev
69
+ ```
70
+
71
+ To test the production build, run:
72
+
73
+ ```bash
74
+ npm run build
75
+ ```
76
+
77
+ To serve the files produced by the build, run:
78
+
79
+ ```bash
80
+ npm run serve
81
+ ```
82
+
83
+ ## Install and configure visualizer-on-tabs
84
+
85
+ ### Configure a flavor to deploy on-tabs
86
+
87
+ Edit the `flavorLayouts`(/usr/local/flavor-builder/config.json) to specify a deployment method for your flavor. For this, you need to add a new entry which key is your flavor name and value is `visualizer-on-tabs`. Example:
88
+
89
+ ```
90
+ ...
91
+ "flavorLayouts": {
92
+ "720p": "minimal-simple-menu",
93
+ "myflavor":"visualizer-on-tabs"
94
+ }
95
+ ...
96
+ ```
97
+
98
+ Add a new rewriteRule
99
+
100
+ ```
101
+ "visualizerOnTabs": {
102
+ "_default": {
103
+ "rocLogin": {
104
+ "url": "https://myloginserver"
105
+ },
106
+ "rewriteRules": [
107
+ {"reg": "^[^/]+$", "replace": "http://myserver.org/rest-on-couch/db/visualizer/$&/view.json"}
108
+ ]
109
+ }
110
+ ...
111
+ ```
112
+
113
+ You would need to edit a view in this flavor, or launch the build manually with the `--forceUpdate` option.
package/bin/build.js ADDED
@@ -0,0 +1,39 @@
1
+ #!/usr/bin/env node
2
+
3
+ /* eslint-disable no-console */
4
+
5
+ import fs from 'node:fs';
6
+ import path from 'node:path';
7
+ import process from 'node:process';
8
+
9
+ import minimist from 'minimist';
10
+
11
+ import build from '../main/index.js';
12
+
13
+ const argv = minimist(process.argv.slice(2));
14
+
15
+ if (!argv.outDir) {
16
+ console.log(`CLI args:
17
+ --outDir - output directory (required)
18
+ --dev - development mode with file watching
19
+ --config - path to JSON config file
20
+ `);
21
+ throw new Error('The --outDir option is required.');
22
+ }
23
+
24
+ const mode = argv.dev ? 'development' : 'production';
25
+ const watch = !!argv.dev;
26
+ const outDir = argv.outDir;
27
+ let config = {};
28
+ if (argv.config) {
29
+ const configFile = path.resolve(argv.config);
30
+ config = JSON.parse(fs.readFileSync(configFile, 'utf8'));
31
+ }
32
+
33
+ const cleanup = await build({ mode, watch, outDir, config });
34
+
35
+ process.on('SIGINT', async () => {
36
+ console.log('Build cancelled on SIGINT');
37
+ cleanup().catch(console.error);
38
+ process.exit(1);
39
+ });
@@ -0,0 +1,8 @@
1
+ // v1.0.0 taken from http://www.lactame.com/lib/iframe-bridge/HEAD/iframe-bridge.js on 2016-12-19
2
+ // Conversion to base64 with http://decodebase64.com/
3
+ // Added //# sourceURL=iframe-bridge-browser.js at the end of the file to help developer tools
4
+ const iframeBridge =
5
+ 'data:application/javascript;base64,' +
6
+ 'LyoqCiAqIGlmcmFtZS1icmlkZ2UgLSBDb21tdW5pY2F0ZSBiZXR3ZWVuIGlmcmFtZXMgYW5kIGEgY29udHJvbCBwYWdlCiAqIEB2ZXJzaW9uIHYxLjAuMAogKiBAbGluayBodHRwczovL2dpdGh1Yi5jb20vY2hlbWluZm8tanMvaWZyYW1lLWJyaWRnZQogKiBAbGljZW5zZSBNSVQKICovCihmdW5jdGlvbiB3ZWJwYWNrVW5pdmVyc2FsTW9kdWxlRGVmaW5pdGlvbihyb290LCBmYWN0b3J5KSB7CglpZih0eXBlb2YgZXhwb3J0cyA9PT0gJ29iamVjdCcgJiYgdHlwZW9mIG1vZHVsZSA9PT0gJ29iamVjdCcpCgkJbW9kdWxlLmV4cG9ydHMgPSBmYWN0b3J5KCk7CgllbHNlIGlmKHR5cGVvZiBkZWZpbmUgPT09ICdmdW5jdGlvbicgJiYgZGVmaW5lLmFtZCkKCQlkZWZpbmUoW10sIGZhY3RvcnkpOwoJZWxzZSBpZih0eXBlb2YgZXhwb3J0cyA9PT0gJ29iamVjdCcpCgkJZXhwb3J0c1siSWZyYW1lQnJpZGdlIl0gPSBmYWN0b3J5KCk7CgllbHNlCgkJcm9vdFsiSWZyYW1lQnJpZGdlIl0gPSBmYWN0b3J5KCk7Cn0pKHRoaXMsIGZ1bmN0aW9uKCkgewpyZXR1cm4gLyoqKioqKi8gKGZ1bmN0aW9uKG1vZHVsZXMpIHsgLy8gd2VicGFja0Jvb3RzdHJhcAovKioqKioqLyAJLy8gVGhlIG1vZHVsZSBjYWNoZQovKioqKioqLyAJdmFyIGluc3RhbGxlZE1vZHVsZXMgPSB7fTsKLyoqKioqKi8KLyoqKioqKi8gCS8vIFRoZSByZXF1aXJlIGZ1bmN0aW9uCi8qKioqKiovIAlmdW5jdGlvbiBfX3dlYnBhY2tfcmVxdWlyZV9fKG1vZHVsZUlkKSB7Ci8qKioqKiovCi8qKioqKiovIAkJLy8gQ2hlY2sgaWYgbW9kdWxlIGlzIGluIGNhY2hlCi8qKioqKiovIAkJaWYoaW5zdGFsbGVkTW9kdWxlc1ttb2R1bGVJZF0pIHsKLyoqKioqKi8gCQkJcmV0dXJuIGluc3RhbGxlZE1vZHVsZXNbbW9kdWxlSWRdLmV4cG9ydHM7Ci8qKioqKiovIAkJfQovKioqKioqLyAJCS8vIENyZWF0ZSBhIG5ldyBtb2R1bGUgKGFuZCBwdXQgaXQgaW50byB0aGUgY2FjaGUpCi8qKioqKiovIAkJdmFyIG1vZHVsZSA9IGluc3RhbGxlZE1vZHVsZXNbbW9kdWxlSWRdID0gewovKioqKioqLyAJCQlpOiBtb2R1bGVJZCwKLyoqKioqKi8gCQkJbDogZmFsc2UsCi8qKioqKiovIAkJCWV4cG9ydHM6IHt9Ci8qKioqKiovIAkJfTsKLyoqKioqKi8KLyoqKioqKi8gCQkvLyBFeGVjdXRlIHRoZSBtb2R1bGUgZnVuY3Rpb24KLyoqKioqKi8gCQltb2R1bGVzW21vZHVsZUlkXS5jYWxsKG1vZHVsZS5leHBvcnRzLCBtb2R1bGUsIG1vZHVsZS5leHBvcnRzLCBfX3dlYnBhY2tfcmVxdWlyZV9fKTsKLyoqKioqKi8KLyoqKioqKi8gCQkvLyBGbGFnIHRoZSBtb2R1bGUgYXMgbG9hZGVkCi8qKioqKiovIAkJbW9kdWxlLmwgPSB0cnVlOwovKioqKioqLwovKioqKioqLyAJCS8vIFJldHVybiB0aGUgZXhwb3J0cyBvZiB0aGUgbW9kdWxlCi8qKioqKiovIAkJcmV0dXJuIG1vZHVsZS5leHBvcnRzOwovKioqKioqLyAJfQovKioqKioqLwovKioqKioqLwovKioqKioqLyAJLy8gZXhwb3NlIHRoZSBtb2R1bGVzIG9iamVjdCAoX193ZWJwYWNrX21vZHVsZXNfXykKLyoqKioqKi8gCV9fd2VicGFja19yZXF1aXJlX18ubSA9IG1vZHVsZXM7Ci8qKioqKiovCi8qKioqKiovIAkvLyBleHBvc2UgdGhlIG1vZHVsZSBjYWNoZQovKioqKioqLyAJX193ZWJwYWNrX3JlcXVpcmVfXy5jID0gaW5zdGFsbGVkTW9kdWxlczsKLyoqKioqKi8KLyoqKioqKi8gCS8vIGRlZmluZSBnZXR0ZXIgZnVuY3Rpb24gZm9yIGhhcm1vbnkgZXhwb3J0cwovKioqKioqLyAJX193ZWJwYWNrX3JlcXVpcmVfXy5kID0gZnVuY3Rpb24oZXhwb3J0cywgbmFtZSwgZ2V0dGVyKSB7Ci8qKioqKiovIAkJaWYoIV9fd2VicGFja19yZXF1aXJlX18ubyhleHBvcnRzLCBuYW1lKSkgewovKioqKioqLyAJCQlPYmplY3QuZGVmaW5lUHJvcGVydHkoZXhwb3J0cywgbmFtZSwgewovKioqKioqLyAJCQkJY29uZmlndXJhYmxlOiBmYWxzZSwKLyoqKioqKi8gCQkJCWVudW1lcmFibGU6IHRydWUsCi8qKioqKiovIAkJCQlnZXQ6IGdldHRlcgovKioqKioqLyAJCQl9KTsKLyoqKioqKi8gCQl9Ci8qKioqKiovIAl9OwovKioqKioqLwovKioqKioqLyAJLy8gZ2V0RGVmYXVsdEV4cG9ydCBmdW5jdGlvbiBmb3IgY29tcGF0aWJpbGl0eSB3aXRoIG5vbi1oYXJtb255IG1vZHVsZXMKLyoqKioqKi8gCV9fd2VicGFja19yZXF1aXJlX18ubiA9IGZ1bmN0aW9uKG1vZHVsZSkgewovKioqKioqLyAJCXZhciBnZXR0ZXIgPSBtb2R1bGUgJiYgbW9kdWxlLl9fZXNNb2R1bGUgPwovKioqKioqLyAJCQlmdW5jdGlvbiBnZXREZWZhdWx0KCkgeyByZXR1cm4gbW9kdWxlWydkZWZhdWx0J107IH0gOgovKioqKioqLyAJCQlmdW5jdGlvbiBnZXRNb2R1bGVFeHBvcnRzKCkgeyByZXR1cm4gbW9kdWxlOyB9OwovKioqKioqLyAJCV9fd2VicGFja19yZXF1aXJlX18uZChnZXR0ZXIsICdhJywgZ2V0dGVyKTsKLyoqKioqKi8gCQlyZXR1cm4gZ2V0dGVyOwovKioqKioqLyAJfTsKLyoqKioqKi8KLyoqKioqKi8gCS8vIE9iamVjdC5wcm90b3R5cGUuaGFzT3duUHJvcGVydHkuY2FsbAovKioqKioqLyAJX193ZWJwYWNrX3JlcXVpcmVfXy5vID0gZnVuY3Rpb24ob2JqZWN0LCBwcm9wZXJ0eSkgeyByZXR1cm4gT2JqZWN0LnByb3RvdHlwZS5oYXNPd25Qcm9wZXJ0eS5jYWxsKG9iamVjdCwgcHJvcGVydHkpOyB9OwovKioqKioqLwovKioqKioqLyAJLy8gX193ZWJwYWNrX3B1YmxpY19wYXRoX18KLyoqKioqKi8gCV9fd2VicGFja19yZXF1aXJlX18ucCA9ICIiOwovKioqKioqLwovKioqKioqLyAJLy8gTG9hZCBlbnRyeSBtb2R1bGUgYW5kIHJldHVybiBleHBvcnRzCi8qKioqKiovIAlyZXR1cm4gX193ZWJwYWNrX3JlcXVpcmVfXyhfX3dlYnBhY2tfcmVxdWlyZV9fLnMgPSAxKTsKLyoqKioqKi8gfSkKLyoqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKi8KLyoqKioqKi8gKFsKLyogMCAqLwovKioqLyAoZnVuY3Rpb24obW9kdWxlLCBleHBvcnRzLCBfX3dlYnBhY2tfcmVxdWlyZV9fKSB7CgoidXNlIHN0cmljdCI7CgoKLy8gQ29weXJpZ2h0IEpveWVudCwgSW5jLiBhbmQgb3RoZXIgTm9kZSBjb250cmlidXRvcnMuCi8vCi8vIFBlcm1pc3Npb24gaXMgaGVyZWJ5IGdyYW50ZWQsIGZyZWUgb2YgY2hhcmdlLCB0byBhbnkgcGVyc29uIG9idGFpbmluZyBhCi8vIGNvcHkgb2YgdGhpcyBzb2Z0d2FyZSBhbmQgYXNzb2NpYXRlZCBkb2N1bWVudGF0aW9uIGZpbGVzICh0aGUKLy8gIlNvZnR3YXJlIiksIHRvIGRlYWwgaW4gdGhlIFNvZnR3YXJlIHdpdGhvdXQgcmVzdHJpY3Rpb24sIGluY2x1ZGluZwovLyB3aXRob3V0IGxpbWl0YXRpb24gdGhlIHJpZ2h0cyB0byB1c2UsIGNvcHksIG1vZGlmeSwgbWVyZ2UsIHB1Ymxpc2gsCi8vIGRpc3RyaWJ1dGUsIHN1YmxpY2Vuc2UsIGFuZC9vciBzZWxsIGNvcGllcyBvZiB0aGUgU29mdHdhcmUsIGFuZCB0byBwZXJtaXQKLy8gcGVyc29ucyB0byB3aG9tIHRoZSBTb2Z0d2FyZSBpcyBmdXJuaXNoZWQgdG8gZG8gc28sIHN1YmplY3QgdG8gdGhlCi8vIGZvbGxvd2luZyBjb25kaXRpb25zOgovLwovLyBUaGUgYWJvdmUgY29weXJpZ2h0IG5vdGljZSBhbmQgdGhpcyBwZXJtaXNzaW9uIG5vdGljZSBzaGFsbCBiZSBpbmNsdWRlZAovLyBpbiBhbGwgY29waWVzIG9yIHN1YnN0YW50aWFsIHBvcnRpb25zIG9mIHRoZSBTb2Z0d2FyZS4KLy8KLy8gVEhFIFNPRlRXQVJFIElTIFBST1ZJREVEICJBUyBJUyIsIFdJVEhPVVQgV0FSUkFOVFkgT0YgQU5ZIEtJTkQsIEVYUFJFU1MKLy8gT1IgSU1QTElFRCwgSU5DTFVESU5HIEJVVCBOT1QgTElNSVRFRCBUTyBUSEUgV0FSUkFOVElFUyBPRgovLyBNRVJDSEFOVEFCSUxJVFksIEZJVE5FU1MgRk9SIEEgUEFSVElDVUxBUiBQVVJQT1NFIEFORCBOT05JTkZSSU5HRU1FTlQuIElOCi8vIE5PIEVWRU5UIFNIQUxMIFRIRSBBVVRIT1JTIE9SIENPUFlSSUdIVCBIT0xERVJTIEJFIExJQUJMRSBGT1IgQU5ZIENMQUlNLAovLyBEQU1BR0VTIE9SIE9USEVSIExJQUJJTElUWSwgV0hFVEhFUiBJTiBBTiBBQ1RJT04gT0YgQ09OVFJBQ1QsIFRPUlQgT1IKLy8gT1RIRVJXSVNFLCBBUklTSU5HIEZST00sIE9VVCBPRiBPUiBJTiBDT05ORUNUSU9OIFdJVEggVEhFIFNPRlRXQVJFIE9SIFRIRQovLyBVU0UgT1IgT1RIRVIgREVBTElOR1MgSU4gVEhFIFNPRlRXQVJFLgoKZnVuY3Rpb24gRXZlbnRFbWl0dGVyKCkgewogIHRoaXMuX2V2ZW50cyA9IHRoaXMuX2V2ZW50cyB8fCB7fTsKICB0aGlzLl9tYXhMaXN0ZW5lcnMgPSB0aGlzLl9tYXhMaXN0ZW5lcnMgfHwgdW5kZWZpbmVkOwp9Cm1vZHVsZS5leHBvcnRzID0gRXZlbnRFbWl0dGVyOwoKLy8gQmFja3dhcmRzLWNvbXBhdCB3aXRoIG5vZGUgMC4xMC54CkV2ZW50RW1pdHRlci5FdmVudEVtaXR0ZXIgPSBFdmVudEVtaXR0ZXI7CgpFdmVudEVtaXR0ZXIucHJvdG90eXBlLl9ldmVudHMgPSB1bmRlZmluZWQ7CkV2ZW50RW1pdHRlci5wcm90b3R5cGUuX21heExpc3RlbmVycyA9IHVuZGVmaW5lZDsKCi8vIEJ5IGRlZmF1bHQgRXZlbnRFbWl0dGVycyB3aWxsIHByaW50IGEgd2FybmluZyBpZiBtb3JlIHRoYW4gMTAgbGlzdGVuZXJzIGFyZQovLyBhZGRlZCB0byBpdC4gVGhpcyBpcyBhIHVzZWZ1bCBkZWZhdWx0IHdoaWNoIGhlbHBzIGZpbmRpbmcgbWVtb3J5IGxlYWtzLgpFdmVudEVtaXR0ZXIuZGVmYXVsdE1heExpc3RlbmVycyA9IDEwOwoKLy8gT2J2aW91c2x5IG5vdCBhbGwgRW1pdHRlcnMgc2hvdWxkIGJlIGxpbWl0ZWQgdG8gMTAuIFRoaXMgZnVuY3Rpb24gYWxsb3dzCi8vIHRoYXQgdG8gYmUgaW5jcmVhc2VkLiBTZXQgdG8gemVybyBmb3IgdW5saW1pdGVkLgpFdmVudEVtaXR0ZXIucHJvdG90eXBlLnNldE1heExpc3RlbmVycyA9IGZ1bmN0aW9uIChuKSB7CiAgaWYgKCFpc051bWJlcihuKSB8fCBuIDwgMCB8fCBpc05hTihuKSkgdGhyb3cgVHlwZUVycm9yKCduIG11c3QgYmUgYSBwb3NpdGl2ZSBudW1iZXInKTsKICB0aGlzLl9tYXhMaXN0ZW5lcnMgPSBuOwogIHJldHVybiB0aGlzOwp9OwoKRXZlbnRFbWl0dGVyLnByb3RvdHlwZS5lbWl0ID0gZnVuY3Rpb24gKHR5cGUpIHsKICB2YXIgZXIsIGhhbmRsZXIsIGxlbiwgYXJncywgaSwgbGlzdGVuZXJzOwoKICBpZiAoIXRoaXMuX2V2ZW50cykgdGhpcy5fZXZlbnRzID0ge307CgogIC8vIElmIHRoZXJlIGlzIG5vICdlcnJvcicgZXZlbnQgbGlzdGVuZXIgdGhlbiB0aHJvdy4KICBpZiAodHlwZSA9PT0gJ2Vycm9yJykgewogICAgaWYgKCF0aGlzLl9ldmVudHMuZXJyb3IgfHwgaXNPYmplY3QodGhpcy5fZXZlbnRzLmVycm9yKSAmJiAhdGhpcy5fZXZlbnRzLmVycm9yLmxlbmd0aCkgewogICAgICBlciA9IGFyZ3VtZW50c1sxXTsKICAgICAgaWYgKGVyIGluc3RhbmNlb2YgRXJyb3IpIHsKICAgICAgICB0aHJvdyBlcjsgLy8gVW5oYW5kbGVkICdlcnJvcicgZXZlbnQKICAgICAgfSBlbHNlIHsKICAgICAgICAvLyBBdCBsZWFzdCBnaXZlIHNvbWUga2luZCBvZiBjb250ZXh0IHRvIHRoZSB1c2VyCiAgICAgICAgdmFyIGVyciA9IG5ldyBFcnJvcignVW5jYXVnaHQsIHVuc3BlY2lmaWVkICJlcnJvciIgZXZlbnQuICgnICsgZXIgKyAnKScpOwogICAgICAgIGVyci5jb250ZXh0ID0gZXI7CiAgICAgICAgdGhyb3cgZXJyOwogICAgICB9CiAgICB9CiAgfQoKICBoYW5kbGVyID0gdGhpcy5fZXZlbnRzW3R5cGVdOwoKICBpZiAoaXNVbmRlZmluZWQoaGFuZGxlcikpIHJldHVybiBmYWxzZTsKCiAgaWYgKGlzRnVuY3Rpb24oaGFuZGxlcikpIHsKICAgIHN3aXRjaCAoYXJndW1lbnRzLmxlbmd0aCkgewogICAgICAvLyBmYXN0IGNhc2VzCiAgICAgIGNhc2UgMToKICAgICAgICBoYW5kbGVyLmNhbGwodGhpcyk7CiAgICAgICAgYnJlYWs7CiAgICAgIGNhc2UgMjoKICAgICAgICBoYW5kbGVyLmNhbGwodGhpcywgYXJndW1lbnRzWzFdKTsKICAgICAgICBicmVhazsKICAgICAgY2FzZSAzOgogICAgICAgIGhhbmRsZXIuY2FsbCh0aGlzLCBhcmd1bWVudHNbMV0sIGFyZ3VtZW50c1syXSk7CiAgICAgICAgYnJlYWs7CiAgICAgIC8vIHNsb3dlcgogICAgICBkZWZhdWx0OgogICAgICAgIGFyZ3MgPSBBcnJheS5wcm90b3R5cGUuc2xpY2UuY2FsbChhcmd1bWVudHMsIDEpOwogICAgICAgIGhhbmRsZXIuYXBwbHkodGhpcywgYXJncyk7CiAgICB9CiAgfSBlbHNlIGlmIChpc09iamVjdChoYW5kbGVyKSkgewogICAgYXJncyA9IEFycmF5LnByb3RvdHlwZS5zbGljZS5jYWxsKGFyZ3VtZW50cywgMSk7CiAgICBsaXN0ZW5lcnMgPSBoYW5kbGVyLnNsaWNlKCk7CiAgICBsZW4gPSBsaXN0ZW5lcnMubGVuZ3RoOwogICAgZm9yIChpID0gMDsgaSA8IGxlbjsgaSsrKSB7CiAgICAgIGxpc3RlbmVyc1tpXS5hcHBseSh0aGlzLCBhcmdzKTsKICAgIH0KICB9CgogIHJldHVybiB0cnVlOwp9OwoKRXZlbnRFbWl0dGVyLnByb3RvdHlwZS5hZGRMaXN0ZW5lciA9IGZ1bmN0aW9uICh0eXBlLCBsaXN0ZW5lcikgewogIHZhciBtOwoKICBpZiAoIWlzRnVuY3Rpb24obGlzdGVuZXIpKSB0aHJvdyBUeXBlRXJyb3IoJ2xpc3RlbmVyIG11c3QgYmUgYSBmdW5jdGlvbicpOwoKICBpZiAoIXRoaXMuX2V2ZW50cykgdGhpcy5fZXZlbnRzID0ge307CgogIC8vIFRvIGF2b2lkIHJlY3Vyc2lvbiBpbiB0aGUgY2FzZSB0aGF0IHR5cGUgPT09ICJuZXdMaXN0ZW5lciIhIEJlZm9yZQogIC8vIGFkZGluZyBpdCB0byB0aGUgbGlzdGVuZXJzLCBmaXJzdCBlbWl0ICJuZXdMaXN0ZW5lciIuCiAgaWYgKHRoaXMuX2V2ZW50cy5uZXdMaXN0ZW5lcikgdGhpcy5lbWl0KCduZXdMaXN0ZW5lcicsIHR5cGUsIGlzRnVuY3Rpb24obGlzdGVuZXIubGlzdGVuZXIpID8gbGlzdGVuZXIubGlzdGVuZXIgOiBsaXN0ZW5lcik7CgogIGlmICghdGhpcy5fZXZlbnRzW3R5cGVdKQogICAgLy8gT3B0aW1pemUgdGhlIGNhc2Ugb2Ygb25lIGxpc3RlbmVyLiBEb24ndCBuZWVkIHRoZSBleHRyYSBhcnJheSBvYmplY3QuCiAgICB0aGlzLl9ldmVudHNbdHlwZV0gPSBsaXN0ZW5lcjtlbHNlIGlmIChpc09iamVjdCh0aGlzLl9ldmVudHNbdHlwZV0pKQogICAgLy8gSWYgd2UndmUgYWxyZWFkeSBnb3QgYW4gYXJyYXksIGp1c3QgYXBwZW5kLgogICAgdGhpcy5fZXZlbnRzW3R5cGVdLnB1c2gobGlzdGVuZXIpO2Vsc2UKICAgIC8vIEFkZGluZyB0aGUgc2Vjb25kIGVsZW1lbnQsIG5lZWQgdG8gY2hhbmdlIHRvIGFycmF5LgogICAgdGhpcy5fZXZlbnRzW3R5cGVdID0gW3RoaXMuX2V2ZW50c1t0eXBlXSwgbGlzdGVuZXJdOwoKICAvLyBDaGVjayBmb3IgbGlzdGVuZXIgbGVhawogIGlmIChpc09iamVjdCh0aGlzLl9ldmVudHNbdHlwZV0pICYmICF0aGlzLl9ldmVudHNbdHlwZV0ud2FybmVkKSB7CiAgICBpZiAoIWlzVW5kZWZpbmVkKHRoaXMuX21heExpc3RlbmVycykpIHsKICAgICAgbSA9IHRoaXMuX21heExpc3RlbmVyczsKICAgIH0gZWxzZSB7CiAgICAgIG0gPSBFdmVudEVtaXR0ZXIuZGVmYXVsdE1heExpc3RlbmVyczsKICAgIH0KCiAgICBpZiAobSAmJiBtID4gMCAmJiB0aGlzLl9ldmVudHNbdHlwZV0ubGVuZ3RoID4gbSkgewogICAgICB0aGlzLl9ldmVudHNbdHlwZV0ud2FybmVkID0gdHJ1ZTsKICAgICAgY29uc29sZS5lcnJvcignKG5vZGUpIHdhcm5pbmc6IHBvc3NpYmxlIEV2ZW50RW1pdHRlciBtZW1vcnkgJyArICdsZWFrIGRldGVjdGVkLiAlZCBsaXN0ZW5lcnMgYWRkZWQuICcgKyAnVXNlIGVtaXR0ZXIuc2V0TWF4TGlzdGVuZXJzKCkgdG8gaW5jcmVhc2UgbGltaXQuJywgdGhpcy5fZXZlbnRzW3R5cGVdLmxlbmd0aCk7CiAgICAgIGlmICh0eXBlb2YgY29uc29sZS50cmFjZSA9PT0gJ2Z1bmN0aW9uJykgewogICAgICAgIC8vIG5vdCBzdXBwb3J0ZWQgaW4gSUUgMTAKICAgICAgICBjb25zb2xlLnRyYWNlKCk7CiAgICAgIH0KICAgIH0KICB9CgogIHJldHVybiB0aGlzOwp9OwoKRXZlbnRFbWl0dGVyLnByb3RvdHlwZS5vbiA9IEV2ZW50RW1pdHRlci5wcm90b3R5cGUuYWRkTGlzdGVuZXI7CgpFdmVudEVtaXR0ZXIucHJvdG90eXBlLm9uY2UgPSBmdW5jdGlvbiAodHlwZSwgbGlzdGVuZXIpIHsKICBpZiAoIWlzRnVuY3Rpb24obGlzdGVuZXIpKSB0aHJvdyBUeXBlRXJyb3IoJ2xpc3RlbmVyIG11c3QgYmUgYSBmdW5jdGlvbicpOwoKICB2YXIgZmlyZWQgPSBmYWxzZTsKCiAgZnVuY3Rpb24gZygpIHsKICAgIHRoaXMucmVtb3ZlTGlzdGVuZXIodHlwZSwgZyk7CgogICAgaWYgKCFmaXJlZCkgewogICAgICBmaXJlZCA9IHRydWU7CiAgICAgIGxpc3RlbmVyLmFwcGx5KHRoaXMsIGFyZ3VtZW50cyk7CiAgICB9CiAgfQoKICBnLmxpc3RlbmVyID0gbGlzdGVuZXI7CiAgdGhpcy5vbih0eXBlLCBnKTsKCiAgcmV0dXJuIHRoaXM7Cn07CgovLyBlbWl0cyBhICdyZW1vdmVMaXN0ZW5lcicgZXZlbnQgaWZmIHRoZSBsaXN0ZW5lciB3YXMgcmVtb3ZlZApFdmVudEVtaXR0ZXIucHJvdG90eXBlLnJlbW92ZUxpc3RlbmVyID0gZnVuY3Rpb24gKHR5cGUsIGxpc3RlbmVyKSB7CiAgdmFyIGxpc3QsIHBvc2l0aW9uLCBsZW5ndGgsIGk7CgogIGlmICghaXNGdW5jdGlvbihsaXN0ZW5lcikpIHRocm93IFR5cGVFcnJvcignbGlzdGVuZXIgbXVzdCBiZSBhIGZ1bmN0aW9uJyk7CgogIGlmICghdGhpcy5fZXZlbnRzIHx8ICF0aGlzLl9ldmVudHNbdHlwZV0pIHJldHVybiB0aGlzOwoKICBsaXN0ID0gdGhpcy5fZXZlbnRzW3R5cGVdOwogIGxlbmd0aCA9IGxpc3QubGVuZ3RoOwogIHBvc2l0aW9uID0gLTE7CgogIGlmIChsaXN0ID09PSBsaXN0ZW5lciB8fCBpc0Z1bmN0aW9uKGxpc3QubGlzdGVuZXIpICYmIGxpc3QubGlzdGVuZXIgPT09IGxpc3RlbmVyKSB7CiAgICBkZWxldGUgdGhpcy5fZXZlbnRzW3R5cGVdOwogICAgaWYgKHRoaXMuX2V2ZW50cy5yZW1vdmVMaXN0ZW5lcikgdGhpcy5lbWl0KCdyZW1vdmVMaXN0ZW5lcicsIHR5cGUsIGxpc3RlbmVyKTsKICB9IGVsc2UgaWYgKGlzT2JqZWN0KGxpc3QpKSB7CiAgICBmb3IgKGkgPSBsZW5ndGg7IGktLSA+IDA7KSB7CiAgICAgIGlmIChsaXN0W2ldID09PSBsaXN0ZW5lciB8fCBsaXN0W2ldLmxpc3RlbmVyICYmIGxpc3RbaV0ubGlzdGVuZXIgPT09IGxpc3RlbmVyKSB7CiAgICAgICAgcG9zaXRpb24gPSBpOwogICAgICAgIGJyZWFrOwogICAgICB9CiAgICB9CgogICAgaWYgKHBvc2l0aW9uIDwgMCkgcmV0dXJuIHRoaXM7CgogICAgaWYgKGxpc3QubGVuZ3RoID09PSAxKSB7CiAgICAgIGxpc3QubGVuZ3RoID0gMDsKICAgICAgZGVsZXRlIHRoaXMuX2V2ZW50c1t0eXBlXTsKICAgIH0gZWxzZSB7CiAgICAgIGxpc3Quc3BsaWNlKHBvc2l0aW9uLCAxKTsKICAgIH0KCiAgICBpZiAodGhpcy5fZXZlbnRzLnJlbW92ZUxpc3RlbmVyKSB0aGlzLmVtaXQoJ3JlbW92ZUxpc3RlbmVyJywgdHlwZSwgbGlzdGVuZXIpOwogIH0KCiAgcmV0dXJuIHRoaXM7Cn07CgpFdmVudEVtaXR0ZXIucHJvdG90eXBlLnJlbW92ZUFsbExpc3RlbmVycyA9IGZ1bmN0aW9uICh0eXBlKSB7CiAgdmFyIGtleSwgbGlzdGVuZXJzOwoKICBpZiAoIXRoaXMuX2V2ZW50cykgcmV0dXJuIHRoaXM7CgogIC8vIG5vdCBsaXN0ZW5pbmcgZm9yIHJlbW92ZUxpc3RlbmVyLCBubyBuZWVkIHRvIGVtaXQKICBpZiAoIXRoaXMuX2V2ZW50cy5yZW1vdmVMaXN0ZW5lcikgewogICAgaWYgKGFyZ3VtZW50cy5sZW5ndGggPT09IDApIHRoaXMuX2V2ZW50cyA9IHt9O2Vsc2UgaWYgKHRoaXMuX2V2ZW50c1t0eXBlXSkgZGVsZXRlIHRoaXMuX2V2ZW50c1t0eXBlXTsKICAgIHJldHVybiB0aGlzOwogIH0KCiAgLy8gZW1pdCByZW1vdmVMaXN0ZW5lciBmb3IgYWxsIGxpc3RlbmVycyBvbiBhbGwgZXZlbnRzCiAgaWYgKGFyZ3VtZW50cy5sZW5ndGggPT09IDApIHsKICAgIGZvciAoa2V5IGluIHRoaXMuX2V2ZW50cykgewogICAgICBpZiAoa2V5ID09PSAncmVtb3ZlTGlzdGVuZXInKSBjb250aW51ZTsKICAgICAgdGhpcy5yZW1vdmVBbGxMaXN0ZW5lcnMoa2V5KTsKICAgIH0KICAgIHRoaXMucmVtb3ZlQWxsTGlzdGVuZXJzKCdyZW1vdmVMaXN0ZW5lcicpOwogICAgdGhpcy5fZXZlbnRzID0ge307CiAgICByZXR1cm4gdGhpczsKICB9CgogIGxpc3RlbmVycyA9IHRoaXMuX2V2ZW50c1t0eXBlXTsKCiAgaWYgKGlzRnVuY3Rpb24obGlzdGVuZXJzKSkgewogICAgdGhpcy5yZW1vdmVMaXN0ZW5lcih0eXBlLCBsaXN0ZW5lcnMpOwogIH0gZWxzZSBpZiAobGlzdGVuZXJzKSB7CiAgICAvLyBMSUZPIG9yZGVyCiAgICB3aGlsZSAobGlzdGVuZXJzLmxlbmd0aCkgewogICAgICB0aGlzLnJlbW92ZUxpc3RlbmVyKHR5cGUsIGxpc3RlbmVyc1tsaXN0ZW5lcnMubGVuZ3RoIC0gMV0pOwogICAgfQogIH0KICBkZWxldGUgdGhpcy5fZXZlbnRzW3R5cGVdOwoKICByZXR1cm4gdGhpczsKfTsKCkV2ZW50RW1pdHRlci5wcm90b3R5cGUubGlzdGVuZXJzID0gZnVuY3Rpb24gKHR5cGUpIHsKICB2YXIgcmV0OwogIGlmICghdGhpcy5fZXZlbnRzIHx8ICF0aGlzLl9ldmVudHNbdHlwZV0pIHJldCA9IFtdO2Vsc2UgaWYgKGlzRnVuY3Rpb24odGhpcy5fZXZlbnRzW3R5cGVdKSkgcmV0ID0gW3RoaXMuX2V2ZW50c1t0eXBlXV07ZWxzZSByZXQgPSB0aGlzLl9ldmVudHNbdHlwZV0uc2xpY2UoKTsKICByZXR1cm4gcmV0Owp9OwoKRXZlbnRFbWl0dGVyLnByb3RvdHlwZS5saXN0ZW5lckNvdW50ID0gZnVuY3Rpb24gKHR5cGUpIHsKICBpZiAodGhpcy5fZXZlbnRzKSB7CiAgICB2YXIgZXZsaXN0ZW5lciA9IHRoaXMuX2V2ZW50c1t0eXBlXTsKCiAgICBpZiAoaXNGdW5jdGlvbihldmxpc3RlbmVyKSkgcmV0dXJuIDE7ZWxzZSBpZiAoZXZsaXN0ZW5lcikgcmV0dXJuIGV2bGlzdGVuZXIubGVuZ3RoOwogIH0KICByZXR1cm4gMDsKfTsKCkV2ZW50RW1pdHRlci5saXN0ZW5lckNvdW50ID0gZnVuY3Rpb24gKGVtaXR0ZXIsIHR5cGUpIHsKICByZXR1cm4gZW1pdHRlci5saXN0ZW5lckNvdW50KHR5cGUpOwp9OwoKZnVuY3Rpb24gaXNGdW5jdGlvbihhcmcpIHsKICByZXR1cm4gdHlwZW9mIGFyZyA9PT0gJ2Z1bmN0aW9uJzsKfQoKZnVuY3Rpb24gaXNOdW1iZXIoYXJnKSB7CiAgcmV0dXJuIHR5cGVvZiBhcmcgPT09ICdudW1iZXInOwp9CgpmdW5jdGlvbiBpc09iamVjdChhcmcpIHsKICByZXR1cm4gdHlwZW9mIGFyZyA9PT0gJ29iamVjdCcgJiYgYXJnICE9PSBudWxsOwp9CgpmdW5jdGlvbiBpc1VuZGVmaW5lZChhcmcpIHsKICByZXR1cm4gYXJnID09PSB2b2lkIDA7Cn0KCi8qKiovIH0pLAovKiAxICovCi8qKiovIChmdW5jdGlvbihtb2R1bGUsIGV4cG9ydHMsIF9fd2VicGFja19yZXF1aXJlX18pIHsKCiJ1c2Ugc3RyaWN0IjsKCgp2YXIgZGVidWcgPSBfX3dlYnBhY2tfcmVxdWlyZV9fKDIpKCdpZnJhbWUtYnJpZGdlOmlmcmFtZScpOwp2YXIgTWVzc2FnZUhhbmRsZXIgPSBfX3dlYnBhY2tfcmVxdWlyZV9fKDYpOwoKdmFyIG1lc3NhZ2VIYW5kbGVyID0gbmV3IE1lc3NhZ2VIYW5kbGVyKCk7CgptZXNzYWdlSGFuZGxlci5pbml0KHdpbmRvdy5wYXJlbnQpOwp3aW5kb3cuYWRkRXZlbnRMaXN0ZW5lcignbWVzc2FnZScsIGZ1bmN0aW9uIChldmVudCkgewogICAgdHJ5IHsKICAgICAgICB2YXIgZGF0YSA9IEpTT04ucGFyc2UoZXZlbnQuZGF0YSk7CiAgICAgICAgZGVidWcoJ21lc3NhZ2UgcmVjZWl2ZWQnLCBkYXRhKTsKICAgICAgICBtZXNzYWdlSGFuZGxlci5oYW5kbGVNZXNzYWdlKGRhdGEpOwogICAgfSBjYXRjaCAoZSkge30KfSk7CgpleHBvcnRzLnBvc3RNZXNzYWdlID0gZnVuY3Rpb24gKHR5cGUsIG1lc3NhZ2UpIHsKICAgIHJldHVybiBtZXNzYWdlSGFuZGxlci5wb3N0TWVzc2FnZSh0eXBlLCBtZXNzYWdlKTsKfTsKCmV4cG9ydHMub25NZXNzYWdlID0gZnVuY3Rpb24gKGNiKSB7CiAgICBtZXNzYWdlSGFuZGxlci5vbignbWVzc2FnZScsIGNiKTsKfTsKCmV4cG9ydHMucmVhZHkgPSBmdW5jdGlvbiAoKSB7CiAgICBtZXNzYWdlSGFuZGxlci5oYW5kbGVQZW5kaW5nTWVzc2FnZXMoKTsKfTsKCi8qKiovIH0pLAovKiAyICovCi8qKiovIChmdW5jdGlvbihtb2R1bGUsIGV4cG9ydHMsIF9fd2VicGFja19yZXF1aXJlX18pIHsKCiJ1c2Ugc3RyaWN0IjsKLyogV0VCUEFDSyBWQVIgSU5KRUNUSU9OICovKGZ1bmN0aW9uKHByb2Nlc3MpIHsKCi8qKgogKiBUaGlzIGlzIHRoZSB3ZWIgYnJvd3NlciBpbXBsZW1lbnRhdGlvbiBvZiBgZGVidWcoKWAuCiAqCiAqIEV4cG9zZSBgZGVidWcoKWAgYXMgdGhlIG1vZHVsZS4KICovCgpleHBvcnRzID0gbW9kdWxlLmV4cG9ydHMgPSBfX3dlYnBhY2tfcmVxdWlyZV9fKDQpOwpleHBvcnRzLmxvZyA9IGxvZzsKZXhwb3J0cy5mb3JtYXRBcmdzID0gZm9ybWF0QXJnczsKZXhwb3J0cy5zYXZlID0gc2F2ZTsKZXhwb3J0cy5sb2FkID0gbG9hZDsKZXhwb3J0cy51c2VDb2xvcnMgPSB1c2VDb2xvcnM7CmV4cG9ydHMuc3RvcmFnZSA9ICd1bmRlZmluZWQnICE9IHR5cGVvZiBjaHJvbWUgJiYgJ3VuZGVmaW5lZCcgIT0gdHlwZW9mIGNocm9tZS5zdG9yYWdlID8gY2hyb21lLnN0b3JhZ2UubG9jYWwgOiBsb2NhbHN0b3JhZ2UoKTsKCi8qKgogKiBDb2xvcnMuCiAqLwoKZXhwb3J0cy5jb2xvcnMgPSBbJ2xpZ2h0c2VhZ3JlZW4nLCAnZm9yZXN0Z3JlZW4nLCAnZ29sZGVucm9kJywgJ2RvZGdlcmJsdWUnLCAnZGFya29yY2hpZCcsICdjcmltc29uJ107CgovKioKICogQ3VycmVudGx5IG9ubHkgV2ViS2l0LWJhc2VkIFdlYiBJbnNwZWN0b3JzLCBGaXJlZm94ID49IHYzMSwKICogYW5kIHRoZSBGaXJlYnVnIGV4dGVuc2lvbiAoYW55IEZpcmVmb3ggdmVyc2lvbikgYXJlIGtub3duCiAqIHRvIHN1cHBvcnQgIiVjIiBDU1MgY3VzdG9taXphdGlvbnMuCiAqCiAqIFRPRE86IGFkZCBhIGBsb2NhbFN0b3JhZ2VgIHZhcmlhYmxlIHRvIGV4cGxpY2l0bHkgZW5hYmxlL2Rpc2FibGUgY29sb3JzCiAqLwoKZnVuY3Rpb24gdXNlQ29sb3JzKCkgewogIC8vIE5COiBJbiBhbiBFbGVjdHJvbiBwcmVsb2FkIHNjcmlwdCwgZG9jdW1lbnQgd2lsbCBiZSBkZWZpbmVkIGJ1dCBub3QgZnVsbHkKICAvLyBpbml0aWFsaXplZC4gU2luY2Ugd2Uga25vdyB3ZSdyZSBpbiBDaHJvbWUsIHdlJ2xsIGp1c3QgZGV0ZWN0IHRoaXMgY2FzZQogIC8vIGV4cGxpY2l0bHkKICBpZiAodHlwZW9mIHdpbmRvdyAhPT0gJ3VuZGVmaW5lZCcgJiYgd2luZG93LnByb2Nlc3MgJiYgd2luZG93LnByb2Nlc3MudHlwZSA9PT0gJ3JlbmRlcmVyJykgewogICAgcmV0dXJuIHRydWU7CiAgfQoKICAvLyBpcyB3ZWJraXQ/IGh0dHA6Ly9zdGFja292ZXJmbG93LmNvbS9hLzE2NDU5NjA2LzM3Njc3MwogIC8vIGRvY3VtZW50IGlzIHVuZGVmaW5lZCBpbiByZWFjdC1uYXRpdmU6IGh0dHBzOi8vZ2l0aHViLmNvbS9mYWNlYm9vay9yZWFjdC1uYXRpdmUvcHVsbC8xNjMyCiAgcmV0dXJuIHR5cGVvZiBkb2N1bWVudCAhPT0gJ3VuZGVmaW5lZCcgJiYgZG9jdW1lbnQuZG9jdW1lbnRFbGVtZW50ICYmIGRvY3VtZW50LmRvY3VtZW50RWxlbWVudC5zdHlsZSAmJiBkb2N1bWVudC5kb2N1bWVudEVsZW1lbnQuc3R5bGUuV2Via2l0QXBwZWFyYW5jZSB8fAogIC8vIGlzIGZpcmVidWc/IGh0dHA6Ly9zdGFja292ZXJmbG93LmNvbS9hLzM5ODEyMC8zNzY3NzMKICB0eXBlb2Ygd2luZG93ICE9PSAndW5kZWZpbmVkJyAmJiB3aW5kb3cuY29uc29sZSAmJiAod2luZG93LmNvbnNvbGUuZmlyZWJ1ZyB8fCB3aW5kb3cuY29uc29sZS5leGNlcHRpb24gJiYgd2luZG93LmNvbnNvbGUudGFibGUpIHx8CiAgLy8gaXMgZmlyZWZveCA+PSB2MzE/CiAgLy8gaHR0cHM6Ly9kZXZlbG9wZXIubW96aWxsYS5vcmcvZW4tVVMvZG9jcy9Ub29scy9XZWJfQ29uc29sZSNTdHlsaW5nX21lc3NhZ2VzCiAgdHlwZW9mIG5hdmlnYXRvciAhPT0gJ3VuZGVmaW5lZCcgJiYgbmF2aWdhdG9yLnVzZXJBZ2VudCAmJiBuYXZpZ2F0b3IudXNlckFnZW50LnRvTG93ZXJDYXNlKCkubWF0Y2goL2ZpcmVmb3hcLyhcZCspLykgJiYgcGFyc2VJbnQoUmVnRXhwLiQxLCAxMCkgPj0gMzEgfHwKICAvLyBkb3VibGUgY2hlY2sgd2Via2l0IGluIHVzZXJBZ2VudCBqdXN0IGluIGNhc2Ugd2UgYXJlIGluIGEgd29ya2VyCiAgdHlwZW9mIG5hdmlnYXRvciAhPT0gJ3VuZGVmaW5lZCcgJiYgbmF2aWdhdG9yLnVzZXJBZ2VudCAmJiBuYXZpZ2F0b3IudXNlckFnZW50LnRvTG93ZXJDYXNlKCkubWF0Y2goL2FwcGxld2Via2l0XC8oXGQrKS8pOwp9CgovKioKICogTWFwICVqIHRvIGBKU09OLnN0cmluZ2lmeSgpYCwgc2luY2Ugbm8gV2ViIEluc3BlY3RvcnMgZG8gdGhhdCBieSBkZWZhdWx0LgogKi8KCmV4cG9ydHMuZm9ybWF0dGVycy5qID0gZnVuY3Rpb24gKHYpIHsKICB0cnkgewogICAgcmV0dXJuIEpTT04uc3RyaW5naWZ5KHYpOwogIH0gY2F0Y2ggKGVycikgewogICAgcmV0dXJuICdbVW5leHBlY3RlZEpTT05QYXJzZUVycm9yXTogJyArIGVyci5tZXNzYWdlOwogIH0KfTsKCi8qKgogKiBDb2xvcml6ZSBsb2cgYXJndW1lbnRzIGlmIGVuYWJsZWQuCiAqCiAqIEBhcGkgcHVibGljCiAqLwoKZnVuY3Rpb24gZm9ybWF0QXJncyhhcmdzKSB7CiAgdmFyIHVzZUNvbG9ycyA9IHRoaXMudXNlQ29sb3JzOwoKICBhcmdzWzBdID0gKHVzZUNvbG9ycyA/ICclYycgOiAnJykgKyB0aGlzLm5hbWVzcGFjZSArICh1c2VDb2xvcnMgPyAnICVjJyA6ICcgJykgKyBhcmdzWzBdICsgKHVzZUNvbG9ycyA/ICclYyAnIDogJyAnKSArICcrJyArIGV4cG9ydHMuaHVtYW5pemUodGhpcy5kaWZmKTsKCiAgaWYgKCF1c2VDb2xvcnMpIHJldHVybjsKCiAgdmFyIGMgPSAnY29sb3I6ICcgKyB0aGlzLmNvbG9yOwogIGFyZ3Muc3BsaWNlKDEsIDAsIGMsICdjb2xvcjogaW5oZXJpdCcpOwoKICAvLyB0aGUgZmluYWwgIiVjIiBpcyBzb21ld2hhdCB0cmlja3ksIGJlY2F1c2UgdGhlcmUgY291bGQgYmUgb3RoZXIKICAvLyBhcmd1bWVudHMgcGFzc2VkIGVpdGhlciBiZWZvcmUgb3IgYWZ0ZXIgdGhlICVjLCBzbyB3ZSBuZWVkIHRvCiAgLy8gZmlndXJlIG91dCB0aGUgY29ycmVjdCBpbmRleCB0byBpbnNlcnQgdGhlIENTUyBpbnRvCiAgdmFyIGluZGV4ID0gMDsKICB2YXIgbGFzdEMgPSAwOwogIGFyZ3NbMF0ucmVwbGFjZSgvJVthLXpBLVolXS9nLCBmdW5jdGlvbiAobWF0Y2gpIHsKICAgIGlmICgnJSUnID09PSBtYXRjaCkgcmV0dXJuOwogICAgaW5kZXgrKzsKICAgIGlmICgnJWMnID09PSBtYXRjaCkgewogICAgICAvLyB3ZSBvbmx5IGFyZSBpbnRlcmVzdGVkIGluIHRoZSAqbGFzdCogJWMKICAgICAgLy8gKHRoZSB1c2VyIG1heSBoYXZlIHByb3ZpZGVkIHRoZWlyIG93bikKICAgICAgbGFzdEMgPSBpbmRleDsKICAgIH0KICB9KTsKCiAgYXJncy5zcGxpY2UobGFzdEMsIDAsIGMpOwp9CgovKioKICogSW52b2tlcyBgY29uc29sZS5sb2coKWAgd2hlbiBhdmFpbGFibGUuCiAqIE5vLW9wIHdoZW4gYGNvbnNvbGUubG9nYCBpcyBub3QgYSAiZnVuY3Rpb24iLgogKgogKiBAYXBpIHB1YmxpYwogKi8KCmZ1bmN0aW9uIGxvZygpIHsKICAvLyB0aGlzIGhhY2tlcnkgaXMgcmVxdWlyZWQgZm9yIElFOC85LCB3aGVyZQogIC8vIHRoZSBgY29uc29sZS5sb2dgIGZ1bmN0aW9uIGRvZXNuJ3QgaGF2ZSAnYXBwbHknCiAgcmV0dXJuICdvYmplY3QnID09PSB0eXBlb2YgY29uc29sZSAmJiBjb25zb2xlLmxvZyAmJiBGdW5jdGlvbi5wcm90b3R5cGUuYXBwbHkuY2FsbChjb25zb2xlLmxvZywgY29uc29sZSwgYXJndW1lbnRzKTsKfQoKLyoqCiAqIFNhdmUgYG5hbWVzcGFjZXNgLgogKgogKiBAcGFyYW0ge1N0cmluZ30gbmFtZXNwYWNlcwogKiBAYXBpIHByaXZhdGUKICovCgpmdW5jdGlvbiBzYXZlKG5hbWVzcGFjZXMpIHsKICB0cnkgewogICAgaWYgKG51bGwgPT0gbmFtZXNwYWNlcykgewogICAgICBleHBvcnRzLnN0b3JhZ2UucmVtb3ZlSXRlbSgnZGVidWcnKTsKICAgIH0gZWxzZSB7CiAgICAgIGV4cG9ydHMuc3RvcmFnZS5kZWJ1ZyA9IG5hbWVzcGFjZXM7CiAgICB9CiAgfSBjYXRjaCAoZSkge30KfQoKLyoqCiAqIExvYWQgYG5hbWVzcGFjZXNgLgogKgogKiBAcmV0dXJuIHtTdHJpbmd9IHJldHVybnMgdGhlIHByZXZpb3VzbHkgcGVyc2lzdGVkIGRlYnVnIG1vZGVzCiAqIEBhcGkgcHJpdmF0ZQogKi8KCmZ1bmN0aW9uIGxvYWQoKSB7CiAgdmFyIHI7CiAgdHJ5IHsKICAgIHIgPSBleHBvcnRzLnN0b3JhZ2UuZGVidWc7CiAgfSBjYXRjaCAoZSkge30KCiAgLy8gSWYgZGVidWcgaXNuJ3Qgc2V0IGluIExTLCBhbmQgd2UncmUgaW4gRWxlY3Ryb24sIHRyeSB0byBsb2FkICRERUJVRwogIGlmICghciAmJiB0eXBlb2YgcHJvY2VzcyAhPT0gJ3VuZGVmaW5lZCcgJiYgJ2VudicgaW4gcHJvY2VzcykgewogICAgciA9IHByb2Nlc3MuZW52LkRFQlVHOwogIH0KCiAgcmV0dXJuIHI7Cn0KCi8qKgogKiBFbmFibGUgbmFtZXNwYWNlcyBsaXN0ZWQgaW4gYGxvY2FsU3RvcmFnZS5kZWJ1Z2AgaW5pdGlhbGx5LgogKi8KCmV4cG9ydHMuZW5hYmxlKGxvYWQoKSk7CgovKioKICogTG9jYWxzdG9yYWdlIGF0dGVtcHRzIHRvIHJldHVybiB0aGUgbG9jYWxzdG9yYWdlLgogKgogKiBUaGlzIGlzIG5lY2Vzc2FyeSBiZWNhdXNlIHNhZmFyaSB0aHJvd3MKICogd2hlbiBhIHVzZXIgZGlzYWJsZXMgY29va2llcy9sb2NhbHN0b3JhZ2UKICogYW5kIHlvdSBhdHRlbXB0IHRvIGFjY2VzcyBpdC4KICoKICogQHJldHVybiB7TG9jYWxTdG9yYWdlfQogKiBAYXBpIHByaXZhdGUKICovCgpmdW5jdGlvbiBsb2NhbHN0b3JhZ2UoKSB7CiAgdHJ5IHsKICAgIHJldHVybiB3aW5kb3cubG9jYWxTdG9yYWdlOwogIH0gY2F0Y2ggKGUpIHt9Cn0KLyogV0VCUEFDSyBWQVIgSU5KRUNUSU9OICovfS5jYWxsKGV4cG9ydHMsIF9fd2VicGFja19yZXF1aXJlX18oMykpKQoKLyoqKi8gfSksCi8qIDMgKi8KLyoqKi8gKGZ1bmN0aW9uKG1vZHVsZSwgZXhwb3J0cywgX193ZWJwYWNrX3JlcXVpcmVfXykgewoKInVzZSBzdHJpY3QiOwoKCi8vIHNoaW0gZm9yIHVzaW5nIHByb2Nlc3MgaW4gYnJvd3Nlcgp2YXIgcHJvY2VzcyA9IG1vZHVsZS5leHBvcnRzID0ge307CgovLyBjYWNoZWQgZnJvbSB3aGF0ZXZlciBnbG9iYWwgaXMgcHJlc2VudCBzbyB0aGF0IHRlc3QgcnVubmVycyB0aGF0IHN0dWIgaXQKLy8gZG9uJ3QgYnJlYWsgdGhpbmdzLiAgQnV0IHdlIG5lZWQgdG8gd3JhcCBpdCBpbiBhIHRyeSBjYXRjaCBpbiBjYXNlIGl0IGlzCi8vIHdyYXBwZWQgaW4gc3RyaWN0IG1vZGUgY29kZSB3aGljaCBkb2Vzbid0IGRlZmluZSBhbnkgZ2xvYmFscy4gIEl0J3MgaW5zaWRlIGEKLy8gZnVuY3Rpb24gYmVjYXVzZSB0cnkvY2F0Y2hlcyBkZW9wdGltaXplIGluIGNlcnRhaW4gZW5naW5lcy4KCnZhciBjYWNoZWRTZXRUaW1lb3V0Owp2YXIgY2FjaGVkQ2xlYXJUaW1lb3V0OwoKZnVuY3Rpb24gZGVmYXVsdFNldFRpbW91dCgpIHsKICAgIHRocm93IG5ldyBFcnJvcignc2V0VGltZW91dCBoYXMgbm90IGJlZW4gZGVmaW5lZCcpOwp9CmZ1bmN0aW9uIGRlZmF1bHRDbGVhclRpbWVvdXQoKSB7CiAgICB0aHJvdyBuZXcgRXJyb3IoJ2NsZWFyVGltZW91dCBoYXMgbm90IGJlZW4gZGVmaW5lZCcpOwp9CihmdW5jdGlvbiAoKSB7CiAgICB0cnkgewogICAgICAgIGlmICh0eXBlb2Ygc2V0VGltZW91dCA9PT0gJ2Z1bmN0aW9uJykgewogICAgICAgICAgICBjYWNoZWRTZXRUaW1lb3V0ID0gc2V0VGltZW91dDsKICAgICAgICB9IGVsc2UgewogICAgICAgICAgICBjYWNoZWRTZXRUaW1lb3V0ID0gZGVmYXVsdFNldFRpbW91dDsKICAgICAgICB9CiAgICB9IGNhdGNoIChlKSB7CiAgICAgICAgY2FjaGVkU2V0VGltZW91dCA9IGRlZmF1bHRTZXRUaW1vdXQ7CiAgICB9CiAgICB0cnkgewogICAgICAgIGlmICh0eXBlb2YgY2xlYXJUaW1lb3V0ID09PSAnZnVuY3Rpb24nKSB7CiAgICAgICAgICAgIGNhY2hlZENsZWFyVGltZW91dCA9IGNsZWFyVGltZW91dDsKICAgICAgICB9IGVsc2UgewogICAgICAgICAgICBjYWNoZWRDbGVhclRpbWVvdXQgPSBkZWZhdWx0Q2xlYXJUaW1lb3V0OwogICAgICAgIH0KICAgIH0gY2F0Y2ggKGUpIHsKICAgICAgICBjYWNoZWRDbGVhclRpbWVvdXQgPSBkZWZhdWx0Q2xlYXJUaW1lb3V0OwogICAgfQp9KSgpOwpmdW5jdGlvbiBydW5UaW1lb3V0KGZ1bikgewogICAgaWYgKGNhY2hlZFNldFRpbWVvdXQgPT09IHNldFRpbWVvdXQpIHsKICAgICAgICAvL25vcm1hbCBlbnZpcm9tZW50cyBpbiBzYW5lIHNpdHVhdGlvbnMKICAgICAgICByZXR1cm4gc2V0VGltZW91dChmdW4sIDApOwogICAgfQogICAgLy8gaWYgc2V0VGltZW91dCB3YXNuJ3QgYXZhaWxhYmxlIGJ1dCB3YXMgbGF0dGVyIGRlZmluZWQKICAgIGlmICgoY2FjaGVkU2V0VGltZW91dCA9PT0gZGVmYXVsdFNldFRpbW91dCB8fCAhY2FjaGVkU2V0VGltZW91dCkgJiYgc2V0VGltZW91dCkgewogICAgICAgIGNhY2hlZFNldFRpbWVvdXQgPSBzZXRUaW1lb3V0OwogICAgICAgIHJldHVybiBzZXRUaW1lb3V0KGZ1biwgMCk7CiAgICB9CiAgICB0cnkgewogICAgICAgIC8vIHdoZW4gd2hlbiBzb21lYm9keSBoYXMgc2NyZXdlZCB3aXRoIHNldFRpbWVvdXQgYnV0IG5vIEkuRS4gbWFkZG5lc3MKICAgICAgICByZXR1cm4gY2FjaGVkU2V0VGltZW91dChmdW4sIDApOwogICAgfSBjYXRjaCAoZSkgewogICAgICAgIHRyeSB7CiAgICAgICAgICAgIC8vIFdoZW4gd2UgYXJlIGluIEkuRS4gYnV0IHRoZSBzY3JpcHQgaGFzIGJlZW4gZXZhbGVkIHNvIEkuRS4gZG9lc24ndCB0cnVzdCB0aGUgZ2xvYmFsIG9iamVjdCB3aGVuIGNhbGxlZCBub3JtYWxseQogICAgICAgICAgICByZXR1cm4gY2FjaGVkU2V0VGltZW91dC5jYWxsKG51bGwsIGZ1biwgMCk7CiAgICAgICAgfSBjYXRjaCAoZSkgewogICAgICAgICAgICAvLyBzYW1lIGFzIGFib3ZlIGJ1dCB3aGVuIGl0J3MgYSB2ZXJzaW9uIG9mIEkuRS4gdGhhdCBtdXN0IGhhdmUgdGhlIGdsb2JhbCBvYmplY3QgZm9yICd0aGlzJywgaG9wZnVsbHkgb3VyIGNvbnRleHQgY29ycmVjdCBvdGhlcndpc2UgaXQgd2lsbCB0aHJvdyBhIGdsb2JhbCBlcnJvcgogICAgICAgICAgICByZXR1cm4gY2FjaGVkU2V0VGltZW91dC5jYWxsKHRoaXMsIGZ1biwgMCk7CiAgICAgICAgfQogICAgfQp9CmZ1bmN0aW9uIHJ1bkNsZWFyVGltZW91dChtYXJrZXIpIHsKICAgIGlmIChjYWNoZWRDbGVhclRpbWVvdXQgPT09IGNsZWFyVGltZW91dCkgewogICAgICAgIC8vbm9ybWFsIGVudmlyb21lbnRzIGluIHNhbmUgc2l0dWF0aW9ucwogICAgICAgIHJldHVybiBjbGVhclRpbWVvdXQobWFya2VyKTsKICAgIH0KICAgIC8vIGlmIGNsZWFyVGltZW91dCB3YXNuJ3QgYXZhaWxhYmxlIGJ1dCB3YXMgbGF0dGVyIGRlZmluZWQKICAgIGlmICgoY2FjaGVkQ2xlYXJUaW1lb3V0ID09PSBkZWZhdWx0Q2xlYXJUaW1lb3V0IHx8ICFjYWNoZWRDbGVhclRpbWVvdXQpICYmIGNsZWFyVGltZW91dCkgewogICAgICAgIGNhY2hlZENsZWFyVGltZW91dCA9IGNsZWFyVGltZW91dDsKICAgICAgICByZXR1cm4gY2xlYXJUaW1lb3V0KG1hcmtlcik7CiAgICB9CiAgICB0cnkgewogICAgICAgIC8vIHdoZW4gd2hlbiBzb21lYm9keSBoYXMgc2NyZXdlZCB3aXRoIHNldFRpbWVvdXQgYnV0IG5vIEkuRS4gbWFkZG5lc3MKICAgICAgICByZXR1cm4gY2FjaGVkQ2xlYXJUaW1lb3V0KG1hcmtlcik7CiAgICB9IGNhdGNoIChlKSB7CiAgICAgICAgdHJ5IHsKICAgICAgICAgICAgLy8gV2hlbiB3ZSBhcmUgaW4gSS5FLiBidXQgdGhlIHNjcmlwdCBoYXMgYmVlbiBldmFsZWQgc28gSS5FLiBkb2Vzbid0ICB0cnVzdCB0aGUgZ2xvYmFsIG9iamVjdCB3aGVuIGNhbGxlZCBub3JtYWxseQogICAgICAgICAgICByZXR1cm4gY2FjaGVkQ2xlYXJUaW1lb3V0LmNhbGwobnVsbCwgbWFya2VyKTsKICAgICAgICB9IGNhdGNoIChlKSB7CiAgICAgICAgICAgIC8vIHNhbWUgYXMgYWJvdmUgYnV0IHdoZW4gaXQncyBhIHZlcnNpb24gb2YgSS5FLiB0aGF0IG11c3QgaGF2ZSB0aGUgZ2xvYmFsIG9iamVjdCBmb3IgJ3RoaXMnLCBob3BmdWxseSBvdXIgY29udGV4dCBjb3JyZWN0IG90aGVyd2lzZSBpdCB3aWxsIHRocm93IGEgZ2xvYmFsIGVycm9yLgogICAgICAgICAgICAvLyBTb21lIHZlcnNpb25zIG9mIEkuRS4gaGF2ZSBkaWZmZXJlbnQgcnVsZXMgZm9yIGNsZWFyVGltZW91dCB2cyBzZXRUaW1lb3V0CiAgICAgICAgICAgIHJldHVybiBjYWNoZWRDbGVhclRpbWVvdXQuY2FsbCh0aGlzLCBtYXJrZXIpOwogICAgICAgIH0KICAgIH0KfQp2YXIgcXVldWUgPSBbXTsKdmFyIGRyYWluaW5nID0gZmFsc2U7CnZhciBjdXJyZW50UXVldWU7CnZhciBxdWV1ZUluZGV4ID0gLTE7CgpmdW5jdGlvbiBjbGVhblVwTmV4dFRpY2soKSB7CiAgICBpZiAoIWRyYWluaW5nIHx8ICFjdXJyZW50UXVldWUpIHsKICAgICAgICByZXR1cm47CiAgICB9CiAgICBkcmFpbmluZyA9IGZhbHNlOwogICAgaWYgKGN1cnJlbnRRdWV1ZS5sZW5ndGgpIHsKICAgICAgICBxdWV1ZSA9IGN1cnJlbnRRdWV1ZS5jb25jYXQocXVldWUpOwogICAgfSBlbHNlIHsKICAgICAgICBxdWV1ZUluZGV4ID0gLTE7CiAgICB9CiAgICBpZiAocXVldWUubGVuZ3RoKSB7CiAgICAgICAgZHJhaW5RdWV1ZSgpOwogICAgfQp9CgpmdW5jdGlvbiBkcmFpblF1ZXVlKCkgewogICAgaWYgKGRyYWluaW5nKSB7CiAgICAgICAgcmV0dXJuOwogICAgfQogICAgdmFyIHRpbWVvdXQgPSBydW5UaW1lb3V0KGNsZWFuVXBOZXh0VGljayk7CiAgICBkcmFpbmluZyA9IHRydWU7CgogICAgdmFyIGxlbiA9IHF1ZXVlLmxlbmd0aDsKICAgIHdoaWxlIChsZW4pIHsKICAgICAgICBjdXJyZW50UXVldWUgPSBxdWV1ZTsKICAgICAgICBxdWV1ZSA9IFtdOwogICAgICAgIHdoaWxlICgrK3F1ZXVlSW5kZXggPCBsZW4pIHsKICAgICAgICAgICAgaWYgKGN1cnJlbnRRdWV1ZSkgewogICAgICAgICAgICAgICAgY3VycmVudFF1ZXVlW3F1ZXVlSW5kZXhdLnJ1bigpOwogICAgICAgICAgICB9CiAgICAgICAgfQogICAgICAgIHF1ZXVlSW5kZXggPSAtMTsKICAgICAgICBsZW4gPSBxdWV1ZS5sZW5ndGg7CiAgICB9CiAgICBjdXJyZW50UXVldWUgPSBudWxsOwogICAgZHJhaW5pbmcgPSBmYWxzZTsKICAgIHJ1bkNsZWFyVGltZW91dCh0aW1lb3V0KTsKfQoKcHJvY2Vzcy5uZXh0VGljayA9IGZ1bmN0aW9uIChmdW4pIHsKICAgIHZhciBhcmdzID0gbmV3IEFycmF5KGFyZ3VtZW50cy5sZW5ndGggLSAxKTsKICAgIGlmIChhcmd1bWVudHMubGVuZ3RoID4gMSkgewogICAgICAgIGZvciAodmFyIGkgPSAxOyBpIDwgYXJndW1lbnRzLmxlbmd0aDsgaSsrKSB7CiAgICAgICAgICAgIGFyZ3NbaSAtIDFdID0gYXJndW1lbnRzW2ldOwogICAgICAgIH0KICAgIH0KICAgIHF1ZXVlLnB1c2gobmV3IEl0ZW0oZnVuLCBhcmdzKSk7CiAgICBpZiAocXVldWUubGVuZ3RoID09PSAxICYmICFkcmFpbmluZykgewogICAgICAgIHJ1blRpbWVvdXQoZHJhaW5RdWV1ZSk7CiAgICB9Cn07CgovLyB2OCBsaWtlcyBwcmVkaWN0aWJsZSBvYmplY3RzCmZ1bmN0aW9uIEl0ZW0oZnVuLCBhcnJheSkgewogICAgdGhpcy5mdW4gPSBmdW47CiAgICB0aGlzLmFycmF5ID0gYXJyYXk7Cn0KSXRlbS5wcm90b3R5cGUucnVuID0gZnVuY3Rpb24gKCkgewogICAgdGhpcy5mdW4uYXBwbHkobnVsbCwgdGhpcy5hcnJheSk7Cn07CnByb2Nlc3MudGl0bGUgPSAnYnJvd3Nlcic7CnByb2Nlc3MuYnJvd3NlciA9IHRydWU7CnByb2Nlc3MuZW52ID0ge307CnByb2Nlc3MuYXJndiA9IFtdOwpwcm9jZXNzLnZlcnNpb24gPSAnJzsgLy8gZW1wdHkgc3RyaW5nIHRvIGF2b2lkIHJlZ2V4cCBpc3N1ZXMKcHJvY2Vzcy52ZXJzaW9ucyA9IHt9OwoKZnVuY3Rpb24gbm9vcCgpIHt9Cgpwcm9jZXNzLm9uID0gbm9vcDsKcHJvY2Vzcy5hZGRMaXN0ZW5lciA9IG5vb3A7CnByb2Nlc3Mub25jZSA9IG5vb3A7CnByb2Nlc3Mub2ZmID0gbm9vcDsKcHJvY2Vzcy5yZW1vdmVMaXN0ZW5lciA9IG5vb3A7CnByb2Nlc3MucmVtb3ZlQWxsTGlzdGVuZXJzID0gbm9vcDsKcHJvY2Vzcy5lbWl0ID0gbm9vcDsKcHJvY2Vzcy5wcmVwZW5kTGlzdGVuZXIgPSBub29wOwpwcm9jZXNzLnByZXBlbmRPbmNlTGlzdGVuZXIgPSBub29wOwoKcHJvY2Vzcy5saXN0ZW5lcnMgPSBmdW5jdGlvbiAobmFtZSkgewogICAgcmV0dXJuIFtdOwp9OwoKcHJvY2Vzcy5iaW5kaW5nID0gZnVuY3Rpb24gKG5hbWUpIHsKICAgIHRocm93IG5ldyBFcnJvcigncHJvY2Vzcy5iaW5kaW5nIGlzIG5vdCBzdXBwb3J0ZWQnKTsKfTsKCnByb2Nlc3MuY3dkID0gZnVuY3Rpb24gKCkgewogICAgcmV0dXJuICcvJzsKfTsKcHJvY2Vzcy5jaGRpciA9IGZ1bmN0aW9uIChkaXIpIHsKICAgIHRocm93IG5ldyBFcnJvcigncHJvY2Vzcy5jaGRpciBpcyBub3Qgc3VwcG9ydGVkJyk7Cn07CnByb2Nlc3MudW1hc2sgPSBmdW5jdGlvbiAoKSB7CiAgICByZXR1cm4gMDsKfTsKCi8qKiovIH0pLAovKiA0ICovCi8qKiovIChmdW5jdGlvbihtb2R1bGUsIGV4cG9ydHMsIF9fd2VicGFja19yZXF1aXJlX18pIHsKCiJ1c2Ugc3RyaWN0IjsKCgovKioKICogVGhpcyBpcyB0aGUgY29tbW9uIGxvZ2ljIGZvciBib3RoIHRoZSBOb2RlLmpzIGFuZCB3ZWIgYnJvd3NlcgogKiBpbXBsZW1lbnRhdGlvbnMgb2YgYGRlYnVnKClgLgogKgogKiBFeHBvc2UgYGRlYnVnKClgIGFzIHRoZSBtb2R1bGUuCiAqLwoKZXhwb3J0cyA9IG1vZHVsZS5leHBvcnRzID0gY3JlYXRlRGVidWcuZGVidWcgPSBjcmVhdGVEZWJ1Z1snZGVmYXVsdCddID0gY3JlYXRlRGVidWc7CmV4cG9ydHMuY29lcmNlID0gY29lcmNlOwpleHBvcnRzLmRpc2FibGUgPSBkaXNhYmxlOwpleHBvcnRzLmVuYWJsZSA9IGVuYWJsZTsKZXhwb3J0cy5lbmFibGVkID0gZW5hYmxlZDsKZXhwb3J0cy5odW1hbml6ZSA9IF9fd2VicGFja19yZXF1aXJlX18oNSk7CgovKioKICogVGhlIGN1cnJlbnRseSBhY3RpdmUgZGVidWcgbW9kZSBuYW1lcywgYW5kIG5hbWVzIHRvIHNraXAuCiAqLwoKZXhwb3J0cy5uYW1lcyA9IFtdOwpleHBvcnRzLnNraXBzID0gW107CgovKioKICogTWFwIG9mIHNwZWNpYWwgIiVuIiBoYW5kbGluZyBmdW5jdGlvbnMsIGZvciB0aGUgZGVidWcgImZvcm1hdCIgYXJndW1lbnQuCiAqCiAqIFZhbGlkIGtleSBuYW1lcyBhcmUgYSBzaW5nbGUsIGxvd2VyIG9yIHVwcGVyLWNhc2UgbGV0dGVyLCBpLmUuICJuIiBhbmQgIk4iLgogKi8KCmV4cG9ydHMuZm9ybWF0dGVycyA9IHt9OwoKLyoqCiAqIFByZXZpb3VzIGxvZyB0aW1lc3RhbXAuCiAqLwoKdmFyIHByZXZUaW1lOwoKLyoqCiAqIFNlbGVjdCBhIGNvbG9yLgogKiBAcGFyYW0ge1N0cmluZ30gbmFtZXNwYWNlCiAqIEByZXR1cm4ge051bWJlcn0KICogQGFwaSBwcml2YXRlCiAqLwoKZnVuY3Rpb24gc2VsZWN0Q29sb3IobmFtZXNwYWNlKSB7CiAgdmFyIGhhc2ggPSAwLAogICAgICBpOwoKICBmb3IgKGkgaW4gbmFtZXNwYWNlKSB7CiAgICBoYXNoID0gKGhhc2ggPDwgNSkgLSBoYXNoICsgbmFtZXNwYWNlLmNoYXJDb2RlQXQoaSk7CiAgICBoYXNoIHw9IDA7IC8vIENvbnZlcnQgdG8gMzJiaXQgaW50ZWdlcgogIH0KCiAgcmV0dXJuIGV4cG9ydHMuY29sb3JzW01hdGguYWJzKGhhc2gpICUgZXhwb3J0cy5jb2xvcnMubGVuZ3RoXTsKfQoKLyoqCiAqIENyZWF0ZSBhIGRlYnVnZ2VyIHdpdGggdGhlIGdpdmVuIGBuYW1lc3BhY2VgLgogKgogKiBAcGFyYW0ge1N0cmluZ30gbmFtZXNwYWNlCiAqIEByZXR1cm4ge0Z1bmN0aW9ufQogKiBAYXBpIHB1YmxpYwogKi8KCmZ1bmN0aW9uIGNyZWF0ZURlYnVnKG5hbWVzcGFjZSkgewoKICBmdW5jdGlvbiBkZWJ1ZygpIHsKICAgIC8vIGRpc2FibGVkPwogICAgaWYgKCFkZWJ1Zy5lbmFibGVkKSByZXR1cm47CgogICAgdmFyIHNlbGYgPSBkZWJ1ZzsKCiAgICAvLyBzZXQgYGRpZmZgIHRpbWVzdGFtcAogICAgdmFyIGN1cnIgPSArbmV3IERhdGUoKTsKICAgIHZhciBtcyA9IGN1cnIgLSAocHJldlRpbWUgfHwgY3Vycik7CiAgICBzZWxmLmRpZmYgPSBtczsKICAgIHNlbGYucHJldiA9IHByZXZUaW1lOwogICAgc2VsZi5jdXJyID0gY3VycjsKICAgIHByZXZUaW1lID0gY3VycjsKCiAgICAvLyB0dXJuIHRoZSBgYXJndW1lbnRzYCBpbnRvIGEgcHJvcGVyIEFycmF5CiAgICB2YXIgYXJncyA9IG5ldyBBcnJheShhcmd1bWVudHMubGVuZ3RoKTsKICAgIGZvciAodmFyIGkgPSAwOyBpIDwgYXJncy5sZW5ndGg7IGkrKykgewogICAgICBhcmdzW2ldID0gYXJndW1lbnRzW2ldOwogICAgfQoKICAgIGFyZ3NbMF0gPSBleHBvcnRzLmNvZXJjZShhcmdzWzBdKTsKCiAgICBpZiAoJ3N0cmluZycgIT09IHR5cGVvZiBhcmdzWzBdKSB7CiAgICAgIC8vIGFueXRoaW5nIGVsc2UgbGV0J3MgaW5zcGVjdCB3aXRoICVPCiAgICAgIGFyZ3MudW5zaGlmdCgnJU8nKTsKICAgIH0KCiAgICAvLyBhcHBseSBhbnkgYGZvcm1hdHRlcnNgIHRyYW5zZm9ybWF0aW9ucwogICAgdmFyIGluZGV4ID0gMDsKICAgIGFyZ3NbMF0gPSBhcmdzWzBdLnJlcGxhY2UoLyUoW2EtekEtWiVdKS9nLCBmdW5jdGlvbiAobWF0Y2gsIGZvcm1hdCkgewogICAgICAvLyBpZiB3ZSBlbmNvdW50ZXIgYW4gZXNjYXBlZCAlIHRoZW4gZG9uJ3QgaW5jcmVhc2UgdGhlIGFycmF5IGluZGV4CiAgICAgIGlmIChtYXRjaCA9PT0gJyUlJykgcmV0dXJuIG1hdGNoOwogICAgICBpbmRleCsrOwogICAgICB2YXIgZm9ybWF0dGVyID0gZXhwb3J0cy5mb3JtYXR0ZXJzW2Zvcm1hdF07CiAgICAgIGlmICgnZnVuY3Rpb24nID09PSB0eXBlb2YgZm9ybWF0dGVyKSB7CiAgICAgICAgdmFyIHZhbCA9IGFyZ3NbaW5kZXhdOwogICAgICAgIG1hdGNoID0gZm9ybWF0dGVyLmNhbGwoc2VsZiwgdmFsKTsKCiAgICAgICAgLy8gbm93IHdlIG5lZWQgdG8gcmVtb3ZlIGBhcmdzW2luZGV4XWAgc2luY2UgaXQncyBpbmxpbmVkIGluIHRoZSBgZm9ybWF0YAogICAgICAgIGFyZ3Muc3BsaWNlKGluZGV4LCAxKTsKICAgICAgICBpbmRleC0tOwogICAgICB9CiAgICAgIHJldHVybiBtYXRjaDsKICAgIH0pOwoKICAgIC8vIGFwcGx5IGVudi1zcGVjaWZpYyBmb3JtYXR0aW5nIChjb2xvcnMsIGV0Yy4pCiAgICBleHBvcnRzLmZvcm1hdEFyZ3MuY2FsbChzZWxmLCBhcmdzKTsKCiAgICB2YXIgbG9nRm4gPSBkZWJ1Zy5sb2cgfHwgZXhwb3J0cy5sb2cgfHwgY29uc29sZS5sb2cuYmluZChjb25zb2xlKTsKICAgIGxvZ0ZuLmFwcGx5KHNlbGYsIGFyZ3MpOwogIH0KCiAgZGVidWcubmFtZXNwYWNlID0gbmFtZXNwYWNlOwogIGRlYnVnLmVuYWJsZWQgPSBleHBvcnRzLmVuYWJsZWQobmFtZXNwYWNlKTsKICBkZWJ1Zy51c2VDb2xvcnMgPSBleHBvcnRzLnVzZUNvbG9ycygpOwogIGRlYnVnLmNvbG9yID0gc2VsZWN0Q29sb3IobmFtZXNwYWNlKTsKCiAgLy8gZW52LXNwZWNpZmljIGluaXRpYWxpemF0aW9uIGxvZ2ljIGZvciBkZWJ1ZyBpbnN0YW5jZXMKICBpZiAoJ2Z1bmN0aW9uJyA9PT0gdHlwZW9mIGV4cG9ydHMuaW5pdCkgewogICAgZXhwb3J0cy5pbml0KGRlYnVnKTsKICB9CgogIHJldHVybiBkZWJ1ZzsKfQoKLyoqCiAqIEVuYWJsZXMgYSBkZWJ1ZyBtb2RlIGJ5IG5hbWVzcGFjZXMuIFRoaXMgY2FuIGluY2x1ZGUgbW9kZXMKICogc2VwYXJhdGVkIGJ5IGEgY29sb24gYW5kIHdpbGRjYXJkcy4KICoKICogQHBhcmFtIHtTdHJpbmd9IG5hbWVzcGFjZXMKICogQGFwaSBwdWJsaWMKICovCgpmdW5jdGlvbiBlbmFibGUobmFtZXNwYWNlcykgewogIGV4cG9ydHMuc2F2ZShuYW1lc3BhY2VzKTsKCiAgZXhwb3J0cy5uYW1lcyA9IFtdOwogIGV4cG9ydHMuc2tpcHMgPSBbXTsKCiAgdmFyIHNwbGl0ID0gKHR5cGVvZiBuYW1lc3BhY2VzID09PSAnc3RyaW5nJyA/IG5hbWVzcGFjZXMgOiAnJykuc3BsaXQoL1tccyxdKy8pOwogIHZhciBsZW4gPSBzcGxpdC5sZW5ndGg7CgogIGZvciAodmFyIGkgPSAwOyBpIDwgbGVuOyBpKyspIHsKICAgIGlmICghc3BsaXRbaV0pIGNvbnRpbnVlOyAvLyBpZ25vcmUgZW1wdHkgc3RyaW5ncwogICAgbmFtZXNwYWNlcyA9IHNwbGl0W2ldLnJlcGxhY2UoL1wqL2csICcuKj8nKTsKICAgIGlmIChuYW1lc3BhY2VzWzBdID09PSAnLScpIHsKICAgICAgZXhwb3J0cy5za2lwcy5wdXNoKG5ldyBSZWdFeHAoJ14nICsgbmFtZXNwYWNlcy5zdWJzdHIoMSkgKyAnJCcpKTsKICAgIH0gZWxzZSB7CiAgICAgIGV4cG9ydHMubmFtZXMucHVzaChuZXcgUmVnRXhwKCdeJyArIG5hbWVzcGFjZXMgKyAnJCcpKTsKICAgIH0KICB9Cn0KCi8qKgogKiBEaXNhYmxlIGRlYnVnIG91dHB1dC4KICoKICogQGFwaSBwdWJsaWMKICovCgpmdW5jdGlvbiBkaXNhYmxlKCkgewogIGV4cG9ydHMuZW5hYmxlKCcnKTsKfQoKLyoqCiAqIFJldHVybnMgdHJ1ZSBpZiB0aGUgZ2l2ZW4gbW9kZSBuYW1lIGlzIGVuYWJsZWQsIGZhbHNlIG90aGVyd2lzZS4KICoKICogQHBhcmFtIHtTdHJpbmd9IG5hbWUKICogQHJldHVybiB7Qm9vbGVhbn0KICogQGFwaSBwdWJsaWMKICovCgpmdW5jdGlvbiBlbmFibGVkKG5hbWUpIHsKICB2YXIgaSwgbGVuOwogIGZvciAoaSA9IDAsIGxlbiA9IGV4cG9ydHMuc2tpcHMubGVuZ3RoOyBpIDwgbGVuOyBpKyspIHsKICAgIGlmIChleHBvcnRzLnNraXBzW2ldLnRlc3QobmFtZSkpIHsKICAgICAgcmV0dXJuIGZhbHNlOwogICAgfQogIH0KICBmb3IgKGkgPSAwLCBsZW4gPSBleHBvcnRzLm5hbWVzLmxlbmd0aDsgaSA8IGxlbjsgaSsrKSB7CiAgICBpZiAoZXhwb3J0cy5uYW1lc1tpXS50ZXN0KG5hbWUpKSB7CiAgICAgIHJldHVybiB0cnVlOwogICAgfQogIH0KICByZXR1cm4gZmFsc2U7Cn0KCi8qKgogKiBDb2VyY2UgYHZhbGAuCiAqCiAqIEBwYXJhbSB7TWl4ZWR9IHZhbAogKiBAcmV0dXJuIHtNaXhlZH0KICogQGFwaSBwcml2YXRlCiAqLwoKZnVuY3Rpb24gY29lcmNlKHZhbCkgewogIGlmICh2YWwgaW5zdGFuY2VvZiBFcnJvcikgcmV0dXJuIHZhbC5zdGFjayB8fCB2YWwubWVzc2FnZTsKICByZXR1cm4gdmFsOwp9CgovKioqLyB9KSwKLyogNSAqLwovKioqLyAoZnVuY3Rpb24obW9kdWxlLCBleHBvcnRzLCBfX3dlYnBhY2tfcmVxdWlyZV9fKSB7CgoidXNlIHN0cmljdCI7CgoKLyoqCiAqIEhlbHBlcnMuCiAqLwoKdmFyIHMgPSAxMDAwOwp2YXIgbSA9IHMgKiA2MDsKdmFyIGggPSBtICogNjA7CnZhciBkID0gaCAqIDI0Owp2YXIgeSA9IGQgKiAzNjUuMjU7CgovKioKICogUGFyc2Ugb3IgZm9ybWF0IHRoZSBnaXZlbiBgdmFsYC4KICoKICogT3B0aW9uczoKICoKICogIC0gYGxvbmdgIHZlcmJvc2UgZm9ybWF0dGluZyBbZmFsc2VdCiAqCiAqIEBwYXJhbSB7U3RyaW5nfE51bWJlcn0gdmFsCiAqIEBwYXJhbSB7T2JqZWN0fSBbb3B0aW9uc10KICogQHRocm93cyB7RXJyb3J9IHRocm93IGFuIGVycm9yIGlmIHZhbCBpcyBub3QgYSBub24tZW1wdHkgc3RyaW5nIG9yIGEgbnVtYmVyCiAqIEByZXR1cm4ge1N0cmluZ3xOdW1iZXJ9CiAqIEBhcGkgcHVibGljCiAqLwoKbW9kdWxlLmV4cG9ydHMgPSBmdW5jdGlvbiAodmFsLCBvcHRpb25zKSB7CiAgb3B0aW9ucyA9IG9wdGlvbnMgfHwge307CiAgdmFyIHR5cGUgPSB0eXBlb2YgdmFsOwogIGlmICh0eXBlID09PSAnc3RyaW5nJyAmJiB2YWwubGVuZ3RoID4gMCkgewogICAgcmV0dXJuIHBhcnNlKHZhbCk7CiAgfSBlbHNlIGlmICh0eXBlID09PSAnbnVtYmVyJyAmJiBpc05hTih2YWwpID09PSBmYWxzZSkgewogICAgcmV0dXJuIG9wdGlvbnMubG9uZyA/IGZtdExvbmcodmFsKSA6IGZtdFNob3J0KHZhbCk7CiAgfQogIHRocm93IG5ldyBFcnJvcigndmFsIGlzIG5vdCBhIG5vbi1lbXB0eSBzdHJpbmcgb3IgYSB2YWxpZCBudW1iZXIuIHZhbD0nICsgSlNPTi5zdHJpbmdpZnkodmFsKSk7Cn07CgovKioKICogUGFyc2UgdGhlIGdpdmVuIGBzdHJgIGFuZCByZXR1cm4gbWlsbGlzZWNvbmRzLgogKgogKiBAcGFyYW0ge1N0cmluZ30gc3RyCiAqIEByZXR1cm4ge051bWJlcn0KICogQGFwaSBwcml2YXRlCiAqLwoKZnVuY3Rpb24gcGFyc2Uoc3RyKSB7CiAgc3RyID0gU3RyaW5nKHN0cik7CiAgaWYgKHN0ci5sZW5ndGggPiAxMDApIHsKICAgIHJldHVybjsKICB9CiAgdmFyIG1hdGNoID0gL14oKD86XGQrKT9cLj9cZCspICoobWlsbGlzZWNvbmRzP3xtc2Vjcz98bXN8c2Vjb25kcz98c2Vjcz98c3xtaW51dGVzP3xtaW5zP3xtfGhvdXJzP3xocnM/fGh8ZGF5cz98ZHx5ZWFycz98eXJzP3x5KT8kL2kuZXhlYyhzdHIpOwogIGlmICghbWF0Y2gpIHsKICAgIHJldHVybjsKICB9CiAgdmFyIG4gPSBwYXJzZUZsb2F0KG1hdGNoWzFdKTsKICB2YXIgdHlwZSA9IChtYXRjaFsyXSB8fCAnbXMnKS50b0xvd2VyQ2FzZSgpOwogIHN3aXRjaCAodHlwZSkgewogICAgY2FzZSAneWVhcnMnOgogICAgY2FzZSAneWVhcic6CiAgICBjYXNlICd5cnMnOgogICAgY2FzZSAneXInOgogICAgY2FzZSAneSc6CiAgICAgIHJldHVybiBuICogeTsKICAgIGNhc2UgJ2RheXMnOgogICAgY2FzZSAnZGF5JzoKICAgIGNhc2UgJ2QnOgogICAgICByZXR1cm4gbiAqIGQ7CiAgICBjYXNlICdob3Vycyc6CiAgICBjYXNlICdob3VyJzoKICAgIGNhc2UgJ2hycyc6CiAgICBjYXNlICdocic6CiAgICBjYXNlICdoJzoKICAgICAgcmV0dXJuIG4gKiBoOwogICAgY2FzZSAnbWludXRlcyc6CiAgICBjYXNlICdtaW51dGUnOgogICAgY2FzZSAnbWlucyc6CiAgICBjYXNlICdtaW4nOgogICAgY2FzZSAnbSc6CiAgICAgIHJldHVybiBuICogbTsKICAgIGNhc2UgJ3NlY29uZHMnOgogICAgY2FzZSAnc2Vjb25kJzoKICAgIGNhc2UgJ3NlY3MnOgogICAgY2FzZSAnc2VjJzoKICAgIGNhc2UgJ3MnOgogICAgICByZXR1cm4gbiAqIHM7CiAgICBjYXNlICdtaWxsaXNlY29uZHMnOgogICAgY2FzZSAnbWlsbGlzZWNvbmQnOgogICAgY2FzZSAnbXNlY3MnOgogICAgY2FzZSAnbXNlYyc6CiAgICBjYXNlICdtcyc6CiAgICAgIHJldHVybiBuOwogICAgZGVmYXVsdDoKICAgICAgcmV0dXJuIHVuZGVmaW5lZDsKICB9Cn0KCi8qKgogKiBTaG9ydCBmb3JtYXQgZm9yIGBtc2AuCiAqCiAqIEBwYXJhbSB7TnVtYmVyfSBtcwogKiBAcmV0dXJuIHtTdHJpbmd9CiAqIEBhcGkgcHJpdmF0ZQogKi8KCmZ1bmN0aW9uIGZtdFNob3J0KG1zKSB7CiAgaWYgKG1zID49IGQpIHsKICAgIHJldHVybiBNYXRoLnJvdW5kKG1zIC8gZCkgKyAnZCc7CiAgfQogIGlmIChtcyA+PSBoKSB7CiAgICByZXR1cm4gTWF0aC5yb3VuZChtcyAvIGgpICsgJ2gnOwogIH0KICBpZiAobXMgPj0gbSkgewogICAgcmV0dXJuIE1hdGgucm91bmQobXMgLyBtKSArICdtJzsKICB9CiAgaWYgKG1zID49IHMpIHsKICAgIHJldHVybiBNYXRoLnJvdW5kKG1zIC8gcykgKyAncyc7CiAgfQogIHJldHVybiBtcyArICdtcyc7Cn0KCi8qKgogKiBMb25nIGZvcm1hdCBmb3IgYG1zYC4KICoKICogQHBhcmFtIHtOdW1iZXJ9IG1zCiAqIEByZXR1cm4ge1N0cmluZ30KICogQGFwaSBwcml2YXRlCiAqLwoKZnVuY3Rpb24gZm10TG9uZyhtcykgewogIHJldHVybiBwbHVyYWwobXMsIGQsICdkYXknKSB8fCBwbHVyYWwobXMsIGgsICdob3VyJykgfHwgcGx1cmFsKG1zLCBtLCAnbWludXRlJykgfHwgcGx1cmFsKG1zLCBzLCAnc2Vjb25kJykgfHwgbXMgKyAnIG1zJzsKfQoKLyoqCiAqIFBsdXJhbGl6YXRpb24gaGVscGVyLgogKi8KCmZ1bmN0aW9uIHBsdXJhbChtcywgbiwgbmFtZSkgewogIGlmIChtcyA8IG4pIHsKICAgIHJldHVybjsKICB9CiAgaWYgKG1zIDwgbiAqIDEuNSkgewogICAgcmV0dXJuIE1hdGguZmxvb3IobXMgLyBuKSArICcgJyArIG5hbWU7CiAgfQogIHJldHVybiBNYXRoLmNlaWwobXMgLyBuKSArICcgJyArIG5hbWUgKyAncyc7Cn0KCi8qKiovIH0pLAovKiA2ICovCi8qKiovIChmdW5jdGlvbihtb2R1bGUsIGV4cG9ydHMsIF9fd2VicGFja19yZXF1aXJlX18pIHsKCiJ1c2Ugc3RyaWN0IjsKCgp2YXIgTWVzc2FnZSA9IF9fd2VicGFja19yZXF1aXJlX18oNyk7Cgp2YXIgRXZlbnRFbWl0dGVyID0gX193ZWJwYWNrX3JlcXVpcmVfXygwKTsKCnZhciBpZENvdW50ZXIgPSAwOwp2YXIgcG9zdGVkTWVzc2FnZXMgPSBuZXcgTWFwKCk7CgpjbGFzcyBNZXNzYWdlSGFuZGxlciBleHRlbmRzIEV2ZW50RW1pdHRlciB7CiAgICBjb25zdHJ1Y3RvcigpIHsKICAgICAgICBzdXBlcigpOwogICAgICAgIHRoaXMucmVhZHlUb1Bvc3QgPSBmYWxzZTsKICAgICAgICB0aGlzLnJlYWR5VG9IYW5kbGUgPSBmYWxzZTsKICAgICAgICB0aGlzLndpbmRvd0lEID0gbnVsbDsKICAgICAgICB0aGlzLm1lc3NhZ2VTb3VyY2UgPSBudWxsOwogICAgICAgIHRoaXMucGVuZGluZ01lc3NhZ2VzID0gW107CiAgICAgICAgdGhpcy51bmhhbmRsZWRNZXNzYWdlcyA9IFtdOwogICAgfQoKICAgIF9wb3N0TWVzc2FnZVRvU291cmNlKG1lc3NhZ2UpIHsKICAgICAgICB0aGlzLm1lc3NhZ2VTb3VyY2UucG9zdE1lc3NhZ2UoSlNPTi5zdHJpbmdpZnkobWVzc2FnZSksICcqJyk7CiAgICB9CgogICAgaW5pdChtZXNzYWdlU291cmNlKSB7CiAgICAgICAgdGhpcy5yZWFkeVRvUG9zdCA9IHRydWU7CiAgICAgICAgdGhpcy53aW5kb3dJRCA9IERhdGUubm93KCk7CiAgICAgICAgdGhpcy5tZXNzYWdlU291cmNlID0gbWVzc2FnZVNvdXJjZTsKICAgICAgICB0aGlzLl9wb3N0TWVzc2FnZVRvU291cmNlKHsKICAgICAgICAgICAgdHlwZTogJ2FkbWluLmNvbm5lY3QnLAogICAgICAgICAgICB3aW5kb3dJRDogdGhpcy53aW5kb3dJRAogICAgICAgIH0pOwoKICAgICAgICB3aW5kb3cuYWRkRXZlbnRMaXN0ZW5lcigndW5sb2FkJywgKCkgPT4gewogICAgICAgICAgICB0aGlzLm1lc3NhZ2VTb3VyY2UucG9zdE1lc3NhZ2UoSlNPTi5zdHJpbmdpZnkoewogICAgICAgICAgICAgICAgdHlwZTogJ2FkbWluLmRpc2Nvbm5lY3QnLAogICAgICAgICAgICAgICAgd2luZG93SUQ6IHRoaXMud2luZG93SUQKICAgICAgICAgICAgfSksICcqJyk7CiAgICAgICAgfSk7CgogICAgICAgIHRoaXMucG9zdFBlbmRpbmdNZXNzYWdlcygpOwogICAgfQoKICAgIHBvc3RNZXNzYWdlKHR5cGUsIG1lc3NhZ2UpIHsKICAgICAgICB2YXIgaWQgPSArK2lkQ291bnRlcjsKICAgICAgICB2YXIgdG9Qb3N0ID0gewogICAgICAgICAgICB0eXBlLAogICAgICAgICAgICBtZXNzYWdlLAogICAgICAgICAgICBtZXNzYWdlSUQ6IGlkLAogICAgICAgICAgICB3aW5kb3dJRDogdGhpcy53aW5kb3dJRAogICAgICAgIH07CiAgICAgICAgdmFyIHRoZU1lc3NhZ2UgPSBuZXcgTWVzc2FnZShpZCwgdG9Qb3N0KTsKICAgICAgICBpZiAodGhpcy5yZWFkeVRvUG9zdCkgewogICAgICAgICAgICB0aGlzLl9wb3N0TWVzc2FnZVRvU291cmNlKHRvUG9zdCk7CiAgICAgICAgICAgIHBvc3RlZE1lc3NhZ2VzLnNldChpZCwgdGhlTWVzc2FnZSk7CiAgICAgICAgfSBlbHNlIHsKICAgICAgICAgICAgdGhpcy5wZW5kaW5nTWVzc2FnZXMucHVzaCh0aGVNZXNzYWdlKTsKICAgICAgICB9CiAgICAgICAgcmV0dXJuIHRoZU1lc3NhZ2U7CiAgICB9CgogICAgcG9zdFBlbmRpbmdNZXNzYWdlcygpIHsKICAgICAgICBmb3IgKHZhciBtZXNzYWdlIG9mIHRoaXMucGVuZGluZ01lc3NhZ2VzKSB7CiAgICAgICAgICAgIG1lc3NhZ2UuZGF0YS53aW5kb3dJRCA9IHRoaXMud2luZG93SUQ7CiAgICAgICAgICAgIHRoaXMuX3Bvc3RNZXNzYWdlVG9Tb3VyY2UobWVzc2FnZS5kYXRhKTsKICAgICAgICAgICAgcG9zdGVkTWVzc2FnZXMuc2V0KG1lc3NhZ2UuaWQsIG1lc3NhZ2UpOwogICAgICAgIH0KICAgIH0KCiAgICBoYW5kbGVQZW5kaW5nTWVzc2FnZXMoKSB7CiAgICAgICAgdGhpcy5yZWFkeVRvSGFuZGxlID0gdHJ1ZTsKICAgICAgICBmb3IgKHZhciBpID0gMDsgaSA8IHRoaXMudW5oYW5kbGVkTWVzc2FnZXMubGVuZ3RoOyBpKyspIHsKICAgICAgICAgICAgdGhpcy5oYW5kbGVNZXNzYWdlKHRoaXMudW5oYW5kbGVkTWVzc2FnZXNbaV0pOwogICAgICAgIH0KICAgICAgICB0aGlzLnVuaGFuZGxlZE1lc3NhZ2VzID0gW107CiAgICB9CgogICAgaGFuZGxlTWVzc2FnZShkYXRhKSB7CiAgICAgICAgaWYgKCF0aGlzLnJlYWR5VG9IYW5kbGUpIHsKICAgICAgICAgICAgdGhpcy51bmhhbmRsZWRNZXNzYWdlcy5wdXNoKGRhdGEpOwogICAgICAgICAgICByZXR1cm47CiAgICAgICAgfQogICAgICAgIGlmICghcG9zdGVkTWVzc2FnZXMuaGFzKGRhdGEubWVzc2FnZUlEKSkgewogICAgICAgICAgICByZXR1cm4gdGhpcy5lbWl0KCdtZXNzYWdlJywgZGF0YSk7CiAgICAgICAgfQogICAgICAgIHZhciBtZXNzYWdlID0gcG9zdGVkTWVzc2FnZXMuZ2V0KGRhdGEubWVzc2FnZUlEKTsKICAgICAgICBpZiAoZGF0YS5zdGF0dXMpIHsKICAgICAgICAgICAgaWYgKGRhdGEuc3RhdHVzID09PSAnZXJyb3InKSB7CiAgICAgICAgICAgICAgICBtZXNzYWdlLl9yZWplY3QoZGF0YSk7CiAgICAgICAgICAgICAgICBwb3N0ZWRNZXNzYWdlcy5kZWxldGUoZGF0YS5tZXNzYWdlSUQpOwogICAgICAgICAgICB9IGVsc2UgaWYgKGRhdGEuc3RhdHVzID09PSAnc3VjY2VzcycpIHsKICAgICAgICAgICAgICAgIG1lc3NhZ2UuX3Jlc29sdmUoZGF0YSk7CiAgICAgICAgICAgICAgICBwb3N0ZWRNZXNzYWdlcy5kZWxldGUoZGF0YS5tZXNzYWdlSUQpOwogICAgICAgICAgICB9IGVsc2UgewogICAgICAgICAgICAgICAgbWVzc2FnZS5lbWl0KGRhdGEuc3RhdHVzLCBkYXRhKTsKICAgICAgICAgICAgfQogICAgICAgIH0gZWxzZSB7CiAgICAgICAgICAgIC8vIG5vIHN0YXR1cyBpcyBjb25zaWRlcmVkIGEgc3VjY2VzcwogICAgICAgICAgICBtZXNzYWdlLl9yZXNvbHZlKGRhdGEpOwogICAgICAgICAgICBwb3N0ZWRNZXNzYWdlcy5kZWxldGUoZGF0YS5tZXNzYWdlSUQpOwogICAgICAgIH0KICAgIH0KfQoKbW9kdWxlLmV4cG9ydHMgPSBNZXNzYWdlSGFuZGxlcjsKCi8qKiovIH0pLAovKiA3ICovCi8qKiovIChmdW5jdGlvbihtb2R1bGUsIGV4cG9ydHMsIF9fd2VicGFja19yZXF1aXJlX18pIHsKCiJ1c2Ugc3RyaWN0IjsKCgp2YXIgRXZlbnRFbWl0dGVyID0gX193ZWJwYWNrX3JlcXVpcmVfXygwKS5FdmVudEVtaXR0ZXI7CnZhciBwcm9taXNlID0gU3ltYm9sKCk7CgpjbGFzcyBNZXNzYWdlIGV4dGVuZHMgRXZlbnRFbWl0dGVyIHsKICAgIGNvbnN0cnVjdG9yKGlkLCBkYXRhKSB7CiAgICAgICAgc3VwZXIoKTsKICAgICAgICB0aGlzLmlkID0gaWQ7CiAgICAgICAgdGhpcy5kYXRhID0gZGF0YTsKICAgICAgICB0aGlzW3Byb21pc2VdID0gbmV3IFByb21pc2UoKHJlc29sdmUsIHJlamVjdCkgPT4gewogICAgICAgICAgICB0aGlzLl9yZXNvbHZlID0gcmVzb2x2ZTsKICAgICAgICAgICAgdGhpcy5fcmVqZWN0ID0gcmVqZWN0OwogICAgICAgIH0pOwogICAgfQoKICAgIHRoZW4ob25SZXNvbHZlLCBvblJlamVjdCkgewogICAgICAgIHJldHVybiB0aGlzW3Byb21pc2VdLnRoZW4ob25SZXNvbHZlLCBvblJlamVjdCk7CiAgICB9CgogICAgY2F0Y2gob25SZWplY3QpIHsKICAgICAgICByZXR1cm4gdGhpc1twcm9taXNlXS5jYXRjaChvblJlamVjdCk7CiAgICB9Cn0KCm1vZHVsZS5leHBvcnRzID0gTWVzc2FnZTsKCi8qKiovIH0pCi8qKioqKiovIF0pOwp9KTsKLy8jIHNvdXJjZU1hcHBpbmdVUkw9aWZyYW1lLWJyaWRnZS5qcy5tYXA=';
7
+
8
+ export default iframeBridge;
package/main/index.js ADDED
@@ -0,0 +1,174 @@
1
+ /* eslint-disable no-console */
2
+
3
+ import fs from 'node:fs/promises';
4
+ import path from 'node:path';
5
+ import { fileURLToPath } from 'node:url';
6
+
7
+ import _ from 'lodash';
8
+ import visualizer from 'react-visualizer';
9
+ import webpack from 'webpack';
10
+
11
+ import iframeBridge from './iframe-bridge.js';
12
+
13
+ const __dirname = import.meta.dirname;
14
+
15
+ const defaultConfig = {
16
+ title: 'visualizer-on-tabs',
17
+ };
18
+
19
+ async function buildApp(options, outDir, cleanup) {
20
+ const { promise, resolve, reject } = Promise.withResolvers();
21
+ const entries = [{ file: 'app.js' }];
22
+ for (const entry of entries) {
23
+ let config = {
24
+ mode: options.mode === 'development' ? 'development' : 'production',
25
+ context: path.resolve(__dirname, '../'),
26
+ entry: path.resolve(__dirname, '../src', entry.file),
27
+ output: {
28
+ path: outDir,
29
+ filename: entry.file,
30
+ },
31
+ devtool: 'source-map',
32
+ module: {
33
+ rules: [
34
+ {
35
+ test: /\.js$/,
36
+ include: [
37
+ path.resolve(__dirname, '../src'),
38
+ path.resolve(__dirname, '../node_modules/iframe-bridge'),
39
+ ],
40
+ loader: 'babel-loader',
41
+ options: {
42
+ cwd: path.join(__dirname, '..'),
43
+ presets: ['@babel/env', '@babel/react'],
44
+ },
45
+ },
46
+ ],
47
+ },
48
+ };
49
+
50
+ function handleError(err, stats) {
51
+ if (err) {
52
+ if (options.watch) {
53
+ console.error(err.stack, err.message);
54
+ } else {
55
+ reject(err);
56
+ }
57
+ } else {
58
+ // TODO: use node's util.debuglog here and in flavor-builder
59
+ const statsJson = stats.toJson();
60
+
61
+ if (statsJson.errors.length > 0) {
62
+ for (let error of statsJson.errors) {
63
+ console.error(error.message);
64
+ }
65
+ if (!options.watch) {
66
+ reject(
67
+ new Error(
68
+ `Build failed with ${statsJson.errors.length} error(s)`,
69
+ ),
70
+ );
71
+ }
72
+ }
73
+ if (statsJson.warnings.length > 0) {
74
+ for (let warning of statsJson.warnings) {
75
+ console.warn(warning.message);
76
+ }
77
+ }
78
+ console.log(`Build of ${entry.file} successful`);
79
+ if (!options.watch) {
80
+ cleanup()
81
+ .catch(console.error)
82
+ .then(() => resolve(cleanup));
83
+ }
84
+ }
85
+ }
86
+ const instance = webpack(config);
87
+ if (options.watch) {
88
+ instance.watch({ aggregateTimeout: 200 }, handleError);
89
+ // In watch mode we resolve before the first build is done.
90
+ resolve(cleanup);
91
+ } else {
92
+ // With a single run, the handler will resolve / reject the promise.
93
+ instance.run(handleError);
94
+ }
95
+ }
96
+ return promise;
97
+ }
98
+
99
+ export default async (options) => {
100
+ Object.assign(options.config, defaultConfig);
101
+
102
+ const outDir = path.resolve(options.outDir);
103
+ await fs.mkdir(outDir, { recursive: true });
104
+
105
+ const confPath = path.join(__dirname, '../src/config/custom.json');
106
+ console.log('Copying files');
107
+ await Promise.all([
108
+ fs.writeFile(confPath, JSON.stringify(options.config)),
109
+ copyBootstrap(options),
110
+ copyContent(options),
111
+ addIndex(options),
112
+ addVisualizer(options),
113
+ ]);
114
+
115
+ async function cleanup() {
116
+ console.log('Cleaning up');
117
+ // Normally, the file should exist when this is called.
118
+ await fs.unlink(confPath);
119
+ }
120
+
121
+ console.log('Building app');
122
+ await buildApp(options, outDir, cleanup);
123
+
124
+ return cleanup;
125
+ };
126
+
127
+ function copyContent(options) {
128
+ return fs.cp(
129
+ path.join(__dirname, '../src/static'),
130
+ path.join(options.outDir, 'static'),
131
+ {
132
+ recursive: true,
133
+ },
134
+ );
135
+ }
136
+
137
+ async function copyBootstrap(options) {
138
+ const bootstrapCss = fileURLToPath(
139
+ import.meta.resolve('bootstrap/dist/css/bootstrap.min.css'),
140
+ );
141
+ await fs.mkdir(path.join(options.outDir, 'static'), { recursive: true });
142
+ return fs.copyFile(
143
+ bootstrapCss,
144
+ path.join(options.outDir, 'static/bootstrap.min.css'),
145
+ );
146
+ }
147
+
148
+ async function addIndex(options) {
149
+ const content = await fs.readFile(
150
+ path.join(__dirname, '../src/template/index.html'),
151
+ 'utf8',
152
+ );
153
+ const tpl = _.template(content);
154
+ return fs.writeFile(
155
+ path.join(options.outDir, 'index.html'),
156
+ tpl({
157
+ title: options.config.title,
158
+ uniqid: Date.now(),
159
+ }),
160
+ );
161
+ }
162
+
163
+ function addVisualizer(options) {
164
+ const page = visualizer.makeVisualizerPage({
165
+ cdn: options.config.visualizerCDN,
166
+ fallbackVersion: options.config.visualizerFallbackVersion,
167
+ scripts: [
168
+ {
169
+ url: iframeBridge,
170
+ },
171
+ ],
172
+ });
173
+ return fs.writeFile(path.join(options.outDir, 'visualizer.html'), page);
174
+ }
package/package.json ADDED
@@ -0,0 +1,55 @@
1
+ {
2
+ "name": "visualizer-on-tabs",
3
+ "version": "1.0.0",
4
+ "description": "visualizer-on-tabs webpack builder",
5
+ "type": "module",
6
+ "exports": {
7
+ ".": "./main/index.js"
8
+ },
9
+ "bin": "bin/build.js",
10
+ "files": [
11
+ "main",
12
+ "src",
13
+ "bin",
14
+ "!**/*.test.js"
15
+ ],
16
+ "browserslist": [
17
+ "defaults"
18
+ ],
19
+ "scripts": {
20
+ "build": "npx --no-install visualizer-on-tabs --config=./dev.json --outDir=./out",
21
+ "build:dev": "npx --no-install visualizer-on-tabs --dev=1 --config=./dev.json --outDir=./out",
22
+ "clean": "rimraf out",
23
+ "eslint": "eslint bin main src",
24
+ "prettier": "prettier --check ./",
25
+ "prettier-write": "prettier --write ./",
26
+ "serve": "npx serve out/",
27
+ "test": "npm run test-only && npm run eslint && npm run prettier",
28
+ "test-only": "node --test"
29
+ },
30
+ "dependencies": {
31
+ "@babel/core": "^7.28.3",
32
+ "@babel/preset-env": "^7.28.3",
33
+ "@babel/preset-react": "^7.27.1",
34
+ "babel-loader": "^10.0.0",
35
+ "bootstrap": "^5.3.3",
36
+ "iframe-bridge": "^3.0.2",
37
+ "lockr": "^0.8.5",
38
+ "lodash": "^4.17.21",
39
+ "minimist": "^1.2.6",
40
+ "react": "^18.3.1",
41
+ "react-bootstrap": "^3.0.0-beta.3",
42
+ "react-dom": "^18.3.1",
43
+ "react-visualizer": "^3.0.1",
44
+ "webpack": "^5.91.0"
45
+ },
46
+ "devDependencies": {
47
+ "eslint": "^9.34.0",
48
+ "eslint-config-zakodium": "^16.0.0",
49
+ "prettier": "^3.6.2",
50
+ "rimraf": "^6.0.1"
51
+ },
52
+ "volta": {
53
+ "node": "24.7.0"
54
+ }
55
+ }
package/src/app.js ADDED
@@ -0,0 +1,20 @@
1
+ import React from 'react';
2
+ import ReactDOM from 'react-dom/client';
3
+
4
+ import App from './components/App.js';
5
+
6
+ const loc = window.location;
7
+ let hash = loc.hash.slice(1);
8
+ if (!hash) {
9
+ hash = 'main';
10
+ }
11
+
12
+ const props = {
13
+ origin: loc.origin,
14
+ path: hash,
15
+ };
16
+
17
+ const element = React.createElement(App, props);
18
+ ReactDOM.createRoot(document.getElementById('visualizer-on-tabs')).render(
19
+ element,
20
+ );
@@ -0,0 +1,373 @@
1
+ /* eslint-disable no-await-in-loop */
2
+
3
+ import { postMessage, registerHandler } from 'iframe-bridge/main';
4
+ import React from 'react';
5
+ import Tab from 'react-bootstrap/Tab';
6
+ import BTabs from 'react-bootstrap/Tabs';
7
+ import { Visualizer } from 'react-visualizer';
8
+
9
+ import { getConfig } from '../config/config.js';
10
+ import customConfig from '../config/custom.json' with { type: 'json' };
11
+ import Tabs from '../main/Tabs.js';
12
+ import iframeMessageHandler from '../main/iframeMessageHandler.js';
13
+ import * as tabStorage from '../main/tabStorage.js';
14
+ import { rewriteURL } from '../util.js';
15
+
16
+ import Login from './Login.js';
17
+ import TabTitle from './TabTitle.js';
18
+
19
+ const config = getConfig(customConfig);
20
+ let tabInit = Promise.resolve();
21
+ let currentIframe;
22
+
23
+ const pageURL = new URL(window.location);
24
+ const pageQueryParameters = (() => {
25
+ let params = {};
26
+ for (let key of pageURL.searchParams.keys()) {
27
+ params[key] = pageURL.searchParams.get(key);
28
+ }
29
+ return params;
30
+ })();
31
+
32
+ const iframeStyle = { position: 'static', flex: 2, border: 'none' };
33
+
34
+ class App extends React.Component {
35
+ constructor(props) {
36
+ super(props);
37
+ for (let key in config.possibleViews) {
38
+ config.possibleViews[key].id = key;
39
+ }
40
+ this.onActiveTab = this.onActiveTab.bind(this);
41
+
42
+ registerHandler('tab', iframeMessageHandler);
43
+ registerHandler('admin', (data, [level2]) => {
44
+ if (level2 === 'connect' && data.windowID !== undefined) {
45
+ if (!currentIframe) {
46
+ // The iframe was refreshed
47
+ config.possibleViews[this.state.activeTabKey].windowID =
48
+ data.windowID;
49
+ this.sendData(this.state.activeTabKey);
50
+ } else {
51
+ config.possibleViews[currentIframe.id].windowID = data.windowID;
52
+ currentIframe.resolve();
53
+ currentIframe = null;
54
+ }
55
+ }
56
+ });
57
+
58
+ Tabs.on('openTab', (obj) => {
59
+ const options = {};
60
+ ['noFocus', 'noFocusEvent', 'noData', 'load'].forEach((prop) => {
61
+ options[prop] = obj[prop];
62
+ delete obj[prop];
63
+ });
64
+ this.doTab(obj, options);
65
+ });
66
+ Tabs.on('status', this.setTabStatus.bind(this));
67
+ Tabs.on('message', this.sendTabMessage.bind(this));
68
+ Tabs.on('focus', this.focusTab.bind(this));
69
+
70
+ this.visualizerVersion = pageURL.searchParams.get('v');
71
+
72
+ this.state = {
73
+ viewsList: [],
74
+ activeTabKey: 0,
75
+ isConfigLoaded: false,
76
+ };
77
+
78
+ void this.loadTabs();
79
+ }
80
+
81
+ async loadTabs() {
82
+ let firstTab;
83
+ const loadTab = async (view) => {
84
+ if (!firstTab) firstTab = view.id;
85
+ await this.doTab(view, {
86
+ noFocus: true,
87
+ load: config.loadHidden,
88
+ noFocusEvent: true,
89
+ });
90
+ };
91
+ const data = tabStorage.load();
92
+ // Load possible views first
93
+ for (let key in config.possibleViews) {
94
+ let saved;
95
+ if ((saved = data.find((el) => el.id === key))) {
96
+ await loadTab(saved);
97
+ } else {
98
+ await loadTab(config.possibleViews[key]);
99
+ }
100
+ }
101
+
102
+ for (let i = 0; i < data.length; i++) {
103
+ if (!config.possibleViews[data[i].id]) {
104
+ await loadTab(data[i]);
105
+ }
106
+ }
107
+
108
+ // Nothing is focused at this point
109
+ const lastSelected = tabStorage.getSelected();
110
+ if (this.state.viewsList.find((el) => el.id === lastSelected)) {
111
+ await this.showTab(lastSelected);
112
+ } else {
113
+ await this.showTab(firstTab);
114
+ }
115
+ }
116
+
117
+ setTabStatus(data) {
118
+ // Find view with given window ID
119
+ const ids = Object.keys(config.possibleViews);
120
+ let id = ids.find(
121
+ (id) => config.possibleViews[id].windowID === data.windowID,
122
+ );
123
+ if (!id) return;
124
+ let view = config.possibleViews[id];
125
+
126
+ view = this.state.viewsList.find((el) => el.id === view.id);
127
+ if (!view) return;
128
+
129
+ view.status = { ...view.status, ...data.message };
130
+ this.setState((state) => ({
131
+ viewsList: state.viewsList,
132
+ }));
133
+ }
134
+
135
+ sendTabMessage(data) {
136
+ const viewInfo = config.possibleViews[data.id];
137
+ if (viewInfo) {
138
+ postMessage('tab.message', data.message, viewInfo.windowID);
139
+ }
140
+ }
141
+
142
+ async focusTab(tabId) {
143
+ if (this.state.viewsList.find((el) => el.id === tabId)) {
144
+ await this.showTab(tabId, {
145
+ noData: true,
146
+ });
147
+ }
148
+ }
149
+
150
+ async doTab(obj, options) {
151
+ if (!config.possibleViews[obj.id]) {
152
+ config.possibleViews[obj.id] = {
153
+ id: obj.id,
154
+ url: obj.url,
155
+ data: obj.data,
156
+ closable: obj.closable,
157
+ rawIframe: obj.rawIframe,
158
+ };
159
+ } else {
160
+ config.possibleViews[obj.id].data = obj.data;
161
+ }
162
+
163
+ if (config.rewriteRules) {
164
+ let newURL = rewriteURL(
165
+ config.rewriteRules,
166
+ config.possibleViews[obj.id].url,
167
+ );
168
+ if (newURL) {
169
+ config.possibleViews[obj.id].rewrittenUrl = newURL;
170
+ }
171
+ }
172
+
173
+ await this.showTab(obj.id, options);
174
+ }
175
+
176
+ async showTab(id, options) {
177
+ options = options || {};
178
+ const sameTab = this.state.activeTabKey === id;
179
+ if (sameTab && !options.force) return;
180
+
181
+ const focusedTabId = options.noFocus ? undefined : id;
182
+ let viewFromList = this.state.viewsList.find((el) => el.id === id);
183
+ const newTab = !viewFromList;
184
+ const viewInfo = config.possibleViews[id];
185
+
186
+ if (!viewInfo) throw new Error('unreachable');
187
+ if (!viewFromList) {
188
+ viewFromList = {
189
+ id,
190
+ url: viewInfo.url,
191
+ rewrittenUrl: viewInfo.rewrittenUrl,
192
+ closable: viewInfo.closable,
193
+ rawIframe: viewInfo.rawIframe,
194
+ };
195
+ this.state.viewsList.push(viewFromList);
196
+ }
197
+ const firstRender =
198
+ (options.load || !options.noFocus) && (newTab || !viewFromList.rendered);
199
+ await tabInit;
200
+ // First render means we expect the render function to initialize a new iframe
201
+ // We need to get the IframeBridge ID of that frame and prevent any other iframes
202
+ // to load during that time
203
+ if (firstRender) {
204
+ // TODO: there is probably a cleaner way than a global promise
205
+ // eslint-disable-next-line require-atomic-updates
206
+ tabInit = new Promise((resolve) => {
207
+ viewFromList.rendered = true;
208
+ this.setState((state) => ({
209
+ activeTabKey: focusedTabId,
210
+ viewsList: state.viewsList,
211
+ }));
212
+
213
+ setTimeout(() => {
214
+ // This will have an effect only if Promise is not yet resolved
215
+ // It prevents completely blocking the interface if there is a problem
216
+ // with that tab
217
+ return resolve();
218
+ }, 3000);
219
+ currentIframe = {
220
+ resolve,
221
+ id,
222
+ };
223
+ });
224
+ await tabInit;
225
+ } else {
226
+ this.setState((state) => ({
227
+ activeTabKey: focusedTabId,
228
+ viewsList: state.viewsList,
229
+ }));
230
+ }
231
+
232
+ // always send data on first render
233
+ if (!options.noData || firstRender) {
234
+ this.sendData(id);
235
+ }
236
+ tabStorage.save(id, viewInfo);
237
+ if (!options.noFocus) {
238
+ tabStorage.saveSelected(id);
239
+ }
240
+ if (!options.noFocusEvent && !sameTab) {
241
+ this.sendTabFocusEvent(focusedTabId);
242
+ }
243
+ }
244
+
245
+ sendData(id) {
246
+ const viewInfo = config.possibleViews[id];
247
+ postMessage(
248
+ 'tab.data',
249
+ { ...viewInfo.data, queryParameters: pageQueryParameters },
250
+ viewInfo.windowID,
251
+ );
252
+ }
253
+
254
+ async removeTab(id) {
255
+ const forbiddenPossibleViews = Object.keys(config.possibleViews);
256
+ tabStorage.remove(id);
257
+ if (forbiddenPossibleViews.indexOf(id) === -1) {
258
+ delete config.possibleViews[id];
259
+ }
260
+ let idx = this.state.viewsList.findIndex((el) => el.id === id);
261
+ if (idx === -1) return;
262
+ this.state.viewsList.splice(idx, 1);
263
+
264
+ let newActiveTab;
265
+ if (id !== this.state.activeTabKey) {
266
+ newActiveTab = this.state.activeTabKey;
267
+ } else {
268
+ const viewsLength = this.state.viewsList.length;
269
+ // Set next active tab
270
+ if (viewsLength > 0) {
271
+ if (idx < viewsLength) {
272
+ newActiveTab = this.state.viewsList[idx].id;
273
+ } else {
274
+ newActiveTab = this.state.viewsList[viewsLength - 1].id;
275
+ }
276
+ }
277
+ }
278
+
279
+ await this.showTab(newActiveTab, {
280
+ noData: true,
281
+ force: true,
282
+ });
283
+ }
284
+
285
+ sendTabFocusEvent(key) {
286
+ if (config.possibleViews[key]) {
287
+ postMessage('tab.focus', {}, config.possibleViews[key].windowID);
288
+ }
289
+ }
290
+
291
+ async onActiveTab(key) {
292
+ await this.showTab(key, {
293
+ noData: true,
294
+ });
295
+ }
296
+
297
+ render() {
298
+ const arr = [];
299
+ for (let view of this.state.viewsList) {
300
+ const closable = view.closable === undefined ? true : view.closable;
301
+ const saved =
302
+ !view.status || view.status.saved === undefined
303
+ ? true
304
+ : view.status.saved;
305
+
306
+ const textStyle = {};
307
+ if (!saved) {
308
+ textStyle.color = 'red';
309
+ }
310
+ const shouldRender = view.rendered || view.id === this.state.activeTabKey;
311
+ let viewPage;
312
+ if (shouldRender) {
313
+ if (view.rawIframe) {
314
+ viewPage = (
315
+ <iframe
316
+ allow="fullscreen; clipboard-read; clipboard-write;"
317
+ src={view.rewrittenUrl || view.url}
318
+ style={iframeStyle}
319
+ />
320
+ );
321
+ } else {
322
+ viewPage = (
323
+ <Visualizer
324
+ url="visualizer.html"
325
+ viewURL={view.rewrittenUrl || view.url}
326
+ version={
327
+ this.visualizerVersion || config.visualizerVersion || 'auto'
328
+ }
329
+ config={config.visualizerConfig}
330
+ style={iframeStyle}
331
+ />
332
+ );
333
+ }
334
+ } else {
335
+ viewPage = <div>Not rendered</div>;
336
+ }
337
+ arr.push(
338
+ <Tab
339
+ title={
340
+ <TabTitle
341
+ textTitle={saved ? null : 'Not saved'}
342
+ textStyle={textStyle}
343
+ name={view.id}
344
+ onTabClosed={closable ? () => this.removeTab(view.id) : null}
345
+ />
346
+ }
347
+ key={view.id}
348
+ eventKey={view.id}
349
+ >
350
+ {viewPage}
351
+ </Tab>,
352
+ );
353
+ }
354
+
355
+ return (
356
+ <div className="visualizer-on-tabs-app">
357
+ <Login config={config} />
358
+ <div className="visualizer-on-tabs-content d-flex flex-column">
359
+ <BTabs
360
+ id="visualizer-on-tabs-tab"
361
+ transition={false}
362
+ activeKey={this.state.activeTabKey}
363
+ onSelect={this.onActiveTab}
364
+ >
365
+ {arr}
366
+ </BTabs>
367
+ </div>
368
+ </div>
369
+ );
370
+ }
371
+ }
372
+
373
+ export default App;
@@ -0,0 +1,92 @@
1
+ import React from 'react';
2
+
3
+ const styles = {
4
+ position: 'fixed',
5
+ right: 20,
6
+ top: 10,
7
+ };
8
+
9
+ class Login extends React.Component {
10
+ constructor(props) {
11
+ super(props);
12
+ const { config } = props;
13
+ this.state = {};
14
+ this.logout = this.logout.bind(this);
15
+ if (!config.rocLogin) return;
16
+
17
+ if (config.rocLogin.urlAbsolute) {
18
+ this.loginUrl = config.rocLogin.urlAbsolute;
19
+ } else {
20
+ this.loginUrl = `${config.rocLogin.url}/auth/login?continue=${
21
+ config.rocLogin.redirect || window.location.href
22
+ }`;
23
+ }
24
+ }
25
+
26
+ componentDidMount() {
27
+ void this.session();
28
+ }
29
+
30
+ async session() {
31
+ if (!this.props.config.rocLogin) return;
32
+ const login = this.props.config.rocLogin;
33
+ const response = await fetch(`${login.url}/auth/session`, {
34
+ credentials: 'include',
35
+ });
36
+ if (response.ok) {
37
+ const body = await response.json();
38
+ if (
39
+ login.auto &&
40
+ (!body.authenticated || (login.user && body.username !== login.user))
41
+ ) {
42
+ window.location.href = this.loginUrl;
43
+ }
44
+ this.setState({
45
+ user: body.username,
46
+ });
47
+ } else {
48
+ this.setState({
49
+ user: null,
50
+ });
51
+ }
52
+ }
53
+
54
+ async logout() {
55
+ if (!this.props.config.rocLogin) return;
56
+ const response = await fetch(
57
+ `${this.props.config.rocLogin.url}/auth/logout`,
58
+ {
59
+ credentials: 'include',
60
+ },
61
+ );
62
+ if (!response.ok) {
63
+ throw new Error(`Unexpected logout response: ${response.statusText}`);
64
+ }
65
+ void this.session();
66
+ }
67
+
68
+ render() {
69
+ if (!this.props.config.rocLogin) {
70
+ return <div />;
71
+ }
72
+ if (!this.state.user || this.state.user === 'anonymous') {
73
+ return (
74
+ <div style={styles}>
75
+ <a href={this.loginUrl}>Login</a>
76
+ </div>
77
+ );
78
+ } else {
79
+ return (
80
+ <div style={styles}>
81
+ {this.state.user}
82
+ &nbsp;
83
+ <a href="#" onClick={this.logout}>
84
+ Logout
85
+ </a>
86
+ </div>
87
+ );
88
+ }
89
+ }
90
+ }
91
+
92
+ export default Login;
@@ -0,0 +1,41 @@
1
+ import React from 'react';
2
+
3
+ class TabTitle extends React.Component {
4
+ constructor(props) {
5
+ super(props);
6
+ this.onClosedClicked = this.onClosedClicked.bind(this);
7
+ }
8
+ onClosedClicked(event) {
9
+ event.stopPropagation();
10
+ event.preventDefault();
11
+ if (this.props.onTabClosed) {
12
+ this.props.onTabClosed.call();
13
+ }
14
+ }
15
+
16
+ render() {
17
+ let closeHandle;
18
+ if (this.props.onTabClosed) {
19
+ closeHandle = (
20
+ <span className="close-tab-glyph" onClick={this.onClosedClicked}>
21
+ ×
22
+ </span>
23
+ );
24
+ } else {
25
+ closeHandle = '';
26
+ }
27
+
28
+ return (
29
+ <div
30
+ title={this.props.textTitle}
31
+ style={this.props.textStyle}
32
+ className="d-flex flex-row gap-2 align-items-baseline"
33
+ >
34
+ {this.props.name}
35
+ {closeHandle}
36
+ </div>
37
+ );
38
+ }
39
+ }
40
+
41
+ export default TabTitle;
@@ -0,0 +1,10 @@
1
+ import defaultConfig from './default.js';
2
+
3
+ export function getConfig(customConfig) {
4
+ const config = { ...defaultConfig, ...customConfig };
5
+ if (config.rocLogin && config.rocLogin.url) {
6
+ // Remove trailing slash
7
+ config.rocLogin.url = config.rocLogin.url.replace(/\/$/, '');
8
+ }
9
+ return config;
10
+ }
@@ -0,0 +1,15 @@
1
+ export default {
2
+ visualizerConfig: {
3
+ debugLevel: 0,
4
+ filters: [],
5
+ modules: {
6
+ folders: ['modules/types'],
7
+ },
8
+ header: false,
9
+ },
10
+ possibleViews: {
11
+ Home: {
12
+ url: '',
13
+ },
14
+ },
15
+ };
@@ -0,0 +1,21 @@
1
+ import { EventEmitter } from 'events';
2
+
3
+ const Tabs = new EventEmitter();
4
+
5
+ Tabs.openTab = function openTab(data) {
6
+ Tabs.emit('openTab', data);
7
+ };
8
+
9
+ Tabs.status = function status(data) {
10
+ Tabs.emit('status', data);
11
+ };
12
+
13
+ Tabs.sendMessage = function sendMessage(message) {
14
+ Tabs.emit('message', message);
15
+ };
16
+
17
+ Tabs.focus = function focus(message) {
18
+ Tabs.emit('focus', message);
19
+ };
20
+
21
+ export default Tabs;
@@ -0,0 +1 @@
1
+ export const version = 1;
@@ -0,0 +1,42 @@
1
+ import Tabs from './Tabs.js';
2
+
3
+ export default function iframeMessageHandler(data, [level2]) {
4
+ let prom;
5
+ switch (level2) {
6
+ case 'open':
7
+ Tabs.openTab(data.message);
8
+ prom = Promise.resolve('done');
9
+ break;
10
+ case 'status':
11
+ Tabs.status(data);
12
+ prom = Promise.resolve('done');
13
+ break;
14
+ case 'message':
15
+ Tabs.sendMessage(data.message);
16
+ prom = Promise.resolve('done');
17
+ break;
18
+ case 'focus':
19
+ Tabs.focus(data.message);
20
+ prom = Promise.resolve('done');
21
+ break;
22
+ default:
23
+ prom = Promise.reject(new Error(`Unknown action: ${level2}}`));
24
+ break;
25
+ }
26
+
27
+ prom.then(
28
+ (message) => {
29
+ data.message = message.data;
30
+ // the iframeMessageHandler callback's `this` is bound by the iframe-bridge library
31
+ // eslint-disable-next-line no-invalid-this
32
+ this.postMessage(data);
33
+ },
34
+ (error) => {
35
+ data.status = 'error';
36
+ data.message = error;
37
+ // the iframeMessageHandler callback's `this` is bound by the iframe-bridge library
38
+ // eslint-disable-next-line no-invalid-this
39
+ this.postMessage(data);
40
+ },
41
+ );
42
+ }
@@ -0,0 +1,63 @@
1
+ import lockr from 'lockr';
2
+
3
+ import { version } from './constants.js';
4
+
5
+ const LOCAL_STORAGE_TAB_DATA = 'vweb-';
6
+ const LOCAL_STORAGE_TAB_IDS = 'vweb1-tab-ids';
7
+ const LOCAL_STORAGE_LAST_TAB = 'vweb1-selected-tab';
8
+
9
+ const storage = {};
10
+
11
+ function isVersionOK(v) {
12
+ return v === undefined || v === version;
13
+ }
14
+
15
+ export function save(tabId, data) {
16
+ if (!tabId) return;
17
+ lockr.sadd(LOCAL_STORAGE_TAB_IDS, tabId);
18
+ let key = LOCAL_STORAGE_TAB_DATA + tabId;
19
+ data.version = version;
20
+ lockr.set(key, data);
21
+ }
22
+
23
+ export function saveSelected(tabId) {
24
+ if (!tabId) return;
25
+ lockr.set(LOCAL_STORAGE_LAST_TAB, tabId);
26
+ }
27
+
28
+ export function getSelected() {
29
+ return lockr.get(LOCAL_STORAGE_LAST_TAB);
30
+ }
31
+
32
+ export function load() {
33
+ let ids = lockr.smembers(LOCAL_STORAGE_TAB_IDS);
34
+ if (!ids) return [];
35
+
36
+ let data = ids.map((id) => {
37
+ return lockr.get(LOCAL_STORAGE_TAB_DATA + id);
38
+ });
39
+
40
+ data.forEach((entry) => {
41
+ if (!isVersionOK(entry.version)) {
42
+ storage.remove(entry.id);
43
+ }
44
+ });
45
+
46
+ data = data.filter((entry) => isVersionOK(entry.version));
47
+
48
+ data.sort(function sortData(a, b) {
49
+ let idxA = ids.indexOf(a.id);
50
+ let idxB = ids.indexOf(b.id);
51
+ if (idxA < idxB) return -1;
52
+ else if (idxB < idxA) return 1;
53
+ else return 0;
54
+ });
55
+ return data;
56
+ }
57
+
58
+ export function remove(id) {
59
+ lockr.srem(LOCAL_STORAGE_TAB_IDS, id);
60
+ lockr.rm(LOCAL_STORAGE_TAB_DATA + id);
61
+ }
62
+
63
+ export default storage;
@@ -0,0 +1,7 @@
1
+ const config = '../config.json';
2
+
3
+ const defaultConfig = {};
4
+
5
+ export function getConfig() {
6
+ return config.visualizerConfig || defaultConfig;
7
+ }
@@ -0,0 +1,75 @@
1
+ html,
2
+ body,
3
+ #visualizer-on-tabs,
4
+ .visualizer-on-tabs-app,
5
+ .visualizer-on-tabs-splash {
6
+ margin: 0;
7
+ padding: 0;
8
+ width: 100%;
9
+ height: 100%;
10
+ font-size: 14px;
11
+ }
12
+
13
+ .visualizer-on-tabs-app {
14
+ display: flex;
15
+ flex-flow: column;
16
+ position: relative;
17
+ }
18
+
19
+ .visualizer-on-tabs-splash {
20
+ position: absolute;
21
+ height: calc(100% - 3px);
22
+ display: flex;
23
+ flex-flow: column;
24
+ background: url(../images/splash.png) no-repeat center center fixed;
25
+ background-size: cover;
26
+ z-index: 1000;
27
+ }
28
+
29
+ .visualizer-on-tabs-login {
30
+ width: 500px;
31
+ margin: auto;
32
+ }
33
+
34
+ .visualizer-on-tabs-login > .form-group {
35
+ display: inline-block;
36
+ }
37
+
38
+ .visualizer-on-tabs-content,
39
+ .tab-content,
40
+ .tab-content > .active,
41
+ .visualizer-on-tabs-splash-bg {
42
+ display: flex;
43
+ flex-direction: column;
44
+ flex: 2;
45
+ }
46
+
47
+ .visualizer-on-tabs-header {
48
+ height: 40px;
49
+ }
50
+
51
+ .visualizer-on-tabs-footer {
52
+ height: 20px;
53
+ }
54
+
55
+ .visualizer-on-tabs-progress {
56
+ width: 250px;
57
+ }
58
+
59
+ .close-tab-glyph {
60
+ font-size: 18px;
61
+ font-weight: bold;
62
+ line-height: 0;
63
+ }
64
+
65
+ .close-tab-glyph:hover {
66
+ color: #ff4136;
67
+ }
68
+
69
+ .nav-tabs > li > a {
70
+ outline: none;
71
+ }
72
+
73
+ .sync-progress-bar {
74
+ background-color: #ff4136;
75
+ }
@@ -0,0 +1,15 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <link rel="stylesheet" href="static/bootstrap.min.css" />
7
+ <link rel="stylesheet" href="static/style.css" />
8
+ <title><%- title %></title>
9
+ </head>
10
+
11
+ <body>
12
+ <div id="visualizer-on-tabs"></div>
13
+ <script src="app.js?_=<%= uniqid %>"></script>
14
+ </body>
15
+ </html>
package/src/util.js ADDED
@@ -0,0 +1,29 @@
1
+ export function rewriteURL(rewriteRules, url) {
2
+ for (let i = 0; i < rewriteRules.length; i++) {
3
+ let rewriteRule = rewriteRules[i];
4
+ if (Array.isArray(rewriteRule)) {
5
+ let tmpUrl = url;
6
+ for (let j = 0; j < rewriteRule.length; j++) {
7
+ let reg = new RegExp(rewriteRule[j].reg);
8
+ let replace = rewriteRule[j].replace;
9
+ let optional = rewriteRule[j].optionalMatch;
10
+ let lastRule = j === rewriteRule.length - 1;
11
+ if (tmpUrl.match(reg) && lastRule) {
12
+ return tmpUrl.replace(reg, replace);
13
+ } else if (tmpUrl.match(reg)) {
14
+ tmpUrl = tmpUrl.replace(reg, replace);
15
+ } else if (!optional) {
16
+ break;
17
+ } else if (optional && lastRule) {
18
+ return tmpUrl;
19
+ }
20
+ }
21
+ } else {
22
+ let reg = new RegExp(rewriteRule.reg);
23
+ if (url.match(reg)) {
24
+ return url.replace(reg, rewriteRule.replace);
25
+ }
26
+ }
27
+ }
28
+ return null;
29
+ }