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 +21 -0
- package/README.md +113 -0
- package/bin/build.js +39 -0
- package/main/iframe-bridge.js +8 -0
- package/main/index.js +174 -0
- package/package.json +55 -0
- package/src/app.js +20 -0
- package/src/components/App.js +373 -0
- package/src/components/Login.js +92 -0
- package/src/components/TabTitle.js +41 -0
- package/src/config/config.js +10 -0
- package/src/config/default.js +15 -0
- package/src/main/Tabs.js +21 -0
- package/src/main/constants.js +1 -0
- package/src/main/iframeMessageHandler.js +42 -0
- package/src/main/tabStorage.js +63 -0
- package/src/main/visualizerConfig.js +7 -0
- package/src/static/style.css +75 -0
- package/src/template/index.html +15 -0
- package/src/util.js +29 -0
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
|
+
|
|
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
|
+
}
|
package/src/main/Tabs.js
ADDED
|
@@ -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,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
|
+
}
|