├── container ├── icon.png ├── res │ ├── connected.png │ └── disconnected.png ├── index.html ├── base.html ├── css │ ├── 1280 │ │ └── style.css │ └── 1920 │ │ └── style.css ├── js │ └── base.js └── LICENSE ├── .npmignore ├── .gitignore ├── lib ├── wrapper │ └── baseFilesystemWrapper.js ├── regexp.js ├── logger.js ├── cli.js ├── deviceConnectHelper.js ├── util.js ├── appLaunchHelper.js ├── certificationHelper.js ├── watchHelper.js ├── hostAppHelper.js └── userInfoHelper.js ├── package.json ├── command ├── watch.js ├── start.js ├── certificate.js └── init.js ├── index.js ├── doc ├── README_zh_HANS.md └── README_zh_HANT.md ├── README.md └── LICENSE /container/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Samsung/Wits/HEAD/container/icon.png -------------------------------------------------------------------------------- /container/res/connected.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Samsung/Wits/HEAD/container/res/connected.png -------------------------------------------------------------------------------- /container/res/disconnected.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Samsung/Wits/HEAD/container/res/disconnected.png -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | package-lock.json 2 | .git 3 | archive/ 4 | container.zip 5 | tools.zip 6 | resource.zip 7 | container/.resource 8 | container/build 9 | .DS_Store 10 | resource 11 | container/js/main.js 12 | container/config.xml 13 | container/WITs.wgt 14 | lib/wrapper/filesystemWrapper.js 15 | lib/wrapper/*.html 16 | test -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | package-lock.json 3 | Wits.wgt 4 | www/ 5 | container.zip 6 | tools.zip 7 | resource.zip 8 | container/.resource 9 | container/build 10 | .DS_Store 11 | resource 12 | container/js/main.js 13 | container/config.xml 14 | container/WITs.wgt 15 | lib/wrapper/filesystemWrapper.js 16 | lib/wrapper/*.html 17 | test -------------------------------------------------------------------------------- /container/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 | 5 | 6 | 7 |
7 |
8 | ## 介绍
9 | - [ENGLISH](/README.md)
10 | - [正體中文](/doc/README_zh_HANT.md)
11 |
12 | ## 安装与配置WITs
13 |
14 | ### 开发者请使用npm
15 |
16 |
17 | #### 1. 全局安装 WITs
18 |
19 | ```sh
20 | $ npm install -g @tizentv/wits
21 | ```
22 |
23 | ### 开发者们请使用git repository
24 |
25 | #### 1. 克隆对应项目
26 | ```sh
27 | $ git clone https://github.com/Samsung/Wits.git
28 | ```
29 |
30 | #### 2. 安装WITs依赖
31 |
32 | ```sh
33 | $ cd ~/{path-to}/Wits
34 | $ npm install -g
35 | ```
36 |
37 | #### 3. 在 Wits 的目录下更改 .witsconfig.json。
38 |
39 | Tizen Studio的证书路径(profiles.xml)是在 **.witsconfig.json** 的 `path` 上配置,
40 | 在mac与Windows系统上默认路径 `path` 都是 `tizen-studio-data/profile/profiles.xml` 。
41 |
42 |
43 | ## **系统要求**
44 |
45 | WITs 需要进一步地在你本地开发机器上配置对应的的步骤。
46 |
47 | #### 1. 打开 **`Terminal(终端器)` on MacOS / Linux** or **`CMD (命令提示符)` / `PowerShell` on Windows**
48 |
49 | #### 2. 安装 Node.js 和 Git (推荐 v7.10.1 ~)
50 |
51 | 我们不能很好告诉你这些安装步骤,因为有太多方法与开发者各有自己的性能配置,但我们推荐你使用一些,例如 `nvm` 或 `asdf` 等的项目管理器去管理不同的Node.js 版本去控制你的代码项目。
52 |
53 |
54 | #### 3. 安装最新的 [Samsung Tizen Studio](http://developer.samsung.com/tv).
55 |
56 |
57 | #### 4. 打开你的Samsung电视上的开发者模式:
58 | - 1 使用你的三星遥控器,按 `Home` 的按键。
59 | - 2 移动所选到 `Apps` 的按键并按 `Enter/OK`。
60 | - 3 如当前显示`Apps` 屏幕,依次在遥控器上按`1` `2` `3` `4` `5`并同时弹出`开发模式对话框`,如果不成功或不出现,再一次尝试。
61 | - 4 当开发者对话框出现, 切换并点击按钮 `On` 和 输入你的开发机器所对应的IP地址。
62 |
63 | ## WITs 细节
64 |
65 | ### WITs所使用的项目结构
66 |
67 |
68 | .witsconfig.json, .witsignore 只需这两个文件增加到你的tizen web项目。
69 |
70 | ### WITs 命令选项
71 |
72 |
73 | #### 1. `wits`
74 |
75 | 显示当前可供你选择的那些命令
76 |
77 | #### 2. `wits -i` / `wits --init`
78 |
79 | 对于正在配置的 WITs
80 | 请留意及注意,只需第一次运行在你的 tizen项目即可。
81 | .witsconfig.json 和 .witsignore会同时生成并增加到你的 tizen项目。
82 | 你可以稍后修改对这些文件的配置。
83 |
84 |
85 | #### 3. `wits -s` / `wits --start`
86 |
87 | 这命令是所有都一样,对于连接,安装及运行在TV都是使用实时加载。如`wits -i` 没有在此之前运行,这个是不允许的。
88 |
89 | #### 4. `wits -w` / `wits --watch`
90 |
91 | 对于连接TV时使用是实时加载。
92 | 在连接之后,每次你在 `你的tizen app项目`的改动文件都会即时反应同步你的电视设备。
93 |
94 | ### .witsconfig.json of WITs
95 |
96 | 细节: [关于.witsconfig.json的范例](https://github.com/Samsung/Wits/wiki/Set-Wits-Environment#data-structure-of-witsconfigjson)
97 |
98 | 无论在`Windows` 和 `MacOS`上,都必须认识到路径的唯一符号是分隔符 (**`/`**)。
99 |
100 |
101 | - **connectionInfo** (必选)
102 | - deviceIp [string] : 设备(TV) Ip 地址 (如是调试器,请填写 0.0.0.0)
103 |
104 | - hostIp [string] : Host(PC) Ip 地址
105 | - socketPort [integer] : TV 端口. 他是由WITs随机生成的。
106 | - width [string] : 分辨率宽度
107 | - isDebugMode [boolean] : 如设置为true, chrome调试器会打开并自动运行。如设置为false, 默认无动作。
108 | - **profileInfo** (必选)
109 | - path [string] : Tizen Studio 证书路径。
110 | - **optionalInfo** (可选)
111 | - proxyServer [string] : 如你使用代理,例如:http://255.255.255.255:8080
112 |
113 | ### WITs的.witsignore:
114 |
115 | 有时候如果你不想将你的一些文件,例如`.git` 或 `node_modules`这类文件推送到电视或设备, 你可以通过填写 .witsignore 文件规定这类文件格式,下次你就发现这些文件不会推送到调试的设备之上。
116 | 实现道理与`.gitignore` 一样。
117 |
118 | `.witsignore`的范例:
119 |
120 | ```text
121 | node_modules
122 | .git
123 | deprecated
124 | stglib
125 | ```
126 |
127 | ## 运行你的APP
128 |
129 | 如果你想一步一步按以下设置。
130 | 请关注: [Running Your App using WITs](https://github.com/Samsung/Wits/wiki/Running-Your-App-using-Wits)
131 |
132 | ### wits -i
133 |
134 | 
135 |
136 | ### wits -s
137 |
138 | 
139 |
140 | ### wits -w
141 |
142 | 
143 |
144 | ## FAQ
145 |
146 | - [WITs FAQ](https://github.com/Samsung/Wits/wiki/Frequently-Asked-Questions)
147 | - [怎样使用Chrome inspector的开发模式](https://github.com/Samsung/Wits/wiki/Frequently-Asked-Questions#answer-1)
148 | - [使用proxy之后](https://github.com/Samsung/Wits/wiki/Frequently-Asked-Questions#question-6)
149 | - [怎么去获取你的电视或设备IP](https://github.com/Samsung/Wits/wiki/Frequently-Asked-Questions#question-7)
150 |
151 | ## 支持的设备
152 |
153 | - 2017 Samsung Smart TV (Tizen 3.0)
154 | - 2018 Samsung Smart TV (Tizen 4.0)
155 | - 2019 Samsung Smart TV (Tizen 5.0)
156 |
157 |
158 |
--------------------------------------------------------------------------------
/doc/README_zh_HANT.md:
--------------------------------------------------------------------------------
1 | # WITs
2 | WITs 是一個非常有效的開發工具,很容易幫助開發屬於你的 Tizen web 項目,並支援**2017+ Samsung tv**的機型。
3 | 它可以非常快地將你開發期間的實時代碼推送到電視機上。每次您想檢視項目如何在設備上運行時,都無需經曆再次構建,打包和重新安裝應用程式。我們稱為 `實時開發`。
4 | **WITs 對於你的開發內容有持續性幫助。**
5 |
6 |
7 |
8 | ## 介紹
9 | - [ENGLISH](/README.md)
10 | - [簡體中文](/doc/README_zh_HANS.md)
11 |
12 | ## 安裝與配置WITs
13 |
14 | ### 開發者請使用npm
15 |
16 |
17 | #### 1. 全局安裝 WITs
18 |
19 | ```sh
20 | $ npm install -g @tizentv/wits
21 | ```
22 |
23 | ### 開發者們請使用git repository
24 |
25 | #### 1. 克隆對應項目
26 | ```sh
27 | $ git clone https://github.com/Samsung/Wits.git
28 | ```
29 |
30 | #### 2. 安裝WITs依賴
31 |
32 | ```sh
33 | $ cd ~/{path-to}/Wits
34 | $ npm install -g
35 | ```
36 |
37 | #### 3. 在 Wits 的目錄下更改 .witsconfig.json。
38 |
39 | Tizen Studio的證書路徑(profiles.xml)是在 **.witsconfig.json** 的 `path` 上配置,
40 | 在mac與Windows係統上預設路徑 `path` 都是 `tizen-studio-data/profile/profiles.xml` 。
41 |
42 |
43 | ## **係統要求**
44 |
45 | WITs 需要進一步地在你在地開發機器上配置對應的的步驟。
46 |
47 | #### 1. 打開 **`Terminal(終端器)` on MacOS / Linux** or **`CMD (命令提示符)` / `PowerShell` on Windows**
48 |
49 | #### 2. 安裝 Node.js 和 Git (推薦 v7.10.1 ~)
50 |
51 | 我們不能很好告訴你這些安裝步驟,因為有太多方法與開發者各有自己的性能配置,但我們推薦你使用一些,例如 `nvm` 或 `asdf` 等的項目管理器去管理不同的Node.js 版本去控製你的代碼項目。
52 |
53 |
54 | #### 3. 安裝最新的 [Samsung Tizen Studio](http://developer.samsung.com/tv).
55 |
56 |
57 | #### 4. 打開你的Samsung電視上的開發者模式:
58 | - 1 使用你的三星遙控器,按 `Home` 的按鍵。
59 | - 2 移動所選到 `Apps` 的按鍵並按 `Enter/OK`。
60 | - 3 如當前顯示`Apps` 熒幕,依次在遙控器上按`1` `2` `3` `4` `5`並同時彈出`開發模式對話框`,如果不成功或不出現,再一次嘗試。
61 | - 4 當開發者對話框出現, 切換並點選按鈕 `On` 和 輸入你的開發機器所對應的IP位址。
62 |
63 | ## WITs 細節
64 |
65 | ### WITs所使用的項目結構
66 |
67 |
68 | .witsconfig.json, .witsignore 隻需這兩個文件增加到你的tizen web項目。
69 |
70 | ### WITs 命令選項
71 |
72 |
73 | #### 1. `wits`
74 |
75 | 顯示當前可供你選擇的那些命令
76 |
77 | #### 2. `wits -i` / `wits --init`
78 |
79 | 對於正在配置的 WITs
80 | 請留意及註意,隻需第一次運行在你的 tizen項目即可。
81 | .witsconfig.json 和 .witsignore會同時生成並增加到你的 tizen項目。
82 | 你可以稍後修改對這些文件的配置。
83 |
84 |
85 | #### 3. `wits -s` / `wits --start`
86 |
87 | 這命令是所有都一樣,對於連接,安裝及運行在TV都是使用實時加載。如`wits -i` 冇有在此之前運行,這個是不允許的。
88 |
89 | #### 4. `wits -w` / `wits --watch`
90 |
91 | 對於連接TV時使用是實時加載。
92 | 在連接之後,每次你在 `你的tizen app項目`的改動文件都會即時反應同步你的電視設備。
93 |
94 | ### .witsconfig.json of WITs
95 |
96 | 細節: [關於.witsconfig.json的範例](https://github.com/Samsung/Wits/wiki/Set-Wits-Environment#data-structure-of-witsconfigjson)
97 |
98 | 無論在`Windows` 和 `MacOS`上,都必須認識到路徑的唯一符號是分隔符 (**`/`**)。
99 |
100 |
101 | - **connectionInfo** (必選)
102 | - deviceIp [string] : 設備(TV) Ip 地址 (如是調試器,請填寫 0.0.0.0)
103 |
104 | - hostIp [string] : Host(PC) Ip 地址
105 | - socketPort [integer] : TV 端口. 他是由WITs隨機生成的。
106 | - width [string] : 分辨率寬度
107 | - isDebugMode [boolean] : 如設定為true, chrome調試器會打開並自動運行。如設定為false, 預設無動作。
108 | - **profileInfo** (必選)
109 | - path [string] : Tizen Studio 證書路徑。
110 | - **optionalInfo** (可選)
111 | - proxyServer [string] : 如你使用代理,例如:http://255.255.255.255:8080
112 |
113 | ### WITs的.witsignore:
114 |
115 | 有時候如果你不想將你的一些文件,例如`.git` 或 `node_modules`這類文件推送到電視或設備, 你可以通過填寫 .witsignore 文件規定這類文件格式,下次你就發現這些文件不會推送到調試的設備之上。
116 | 實現道理與`.gitignore` 一樣。
117 |
118 | `.witsignore`的範例:
119 |
120 | ```text
121 | node_modules
122 | .git
123 | deprecated
124 | stglib
125 | ```
126 |
127 | ## 運行你的APP
128 |
129 | 如果你想一步一步按以下設定。
130 | 請關註: [Running Your App using WITs](https://github.com/Samsung/Wits/wiki/Running-Your-App-using-Wits)
131 |
132 | ### wits -i
133 |
134 | 
135 |
136 | ### wits -s
137 |
138 | 
139 |
140 | ### wits -w
141 |
142 | 
143 |
144 | ## FAQ
145 |
146 | - [WITs FAQ](https://github.com/Samsung/Wits/wiki/Frequently-Asked-Questions)
147 | - [怎樣使用Chrome inspector的開發模式](https://github.com/Samsung/Wits/wiki/Frequently-Asked-Questions#answer-1)
148 | - [使用proxy之後](https://github.com/Samsung/Wits/wiki/Frequently-Asked-Questions#question-6)
149 | - [怎麼去獲取你的電視或設備IP](https://github.com/Samsung/Wits/wiki/Frequently-Asked-Questions#question-7)
150 |
151 | ## 支援的設備
152 |
153 | - 2017 Samsung Smart TV (Tizen 3.0)
154 | - 2018 Samsung Smart TV (Tizen 4.0)
155 | - 2019 Samsung Smart TV (Tizen 5.0)
156 |
157 |
158 |
--------------------------------------------------------------------------------
/lib/util.js:
--------------------------------------------------------------------------------
1 | const os = require('os');
2 | const fs = require('fs');
3 | const path = require('path');
4 | const ip = require('ip');
5 | const mkdirp = require('mkdirp');
6 | const tools = require('@tizentv/tools');
7 | const chalk = require('chalk');
8 |
9 | const regExp = require('./regexp.js');
10 | const { logger } = require('./logger');
11 |
12 | const platform = os.platform();
13 | const CURRENT_PROJECT_PATH = process.cwd();
14 |
15 | module.exports = {
16 | WITS_BASE_PATH: __dirname,
17 | CURRENT_PROJECT_PATH: CURRENT_PROJECT_PATH,
18 | PROXY: '',
19 | RESOURCE_PATH: (() => {
20 | if (platform === 'win32') {
21 | return path.resolve(__dirname, '../', 'resource');
22 | } else {
23 | return path.resolve(os.homedir(), '.wits', 'resource');
24 | }
25 | })(),
26 | TOOLS_SDB_PATH: '',
27 | PLATFORM: platform,
28 | ISVERVOSE: false,
29 | initTools: async () => {
30 | module.exports.TOOLS_SDB_PATH = await tools.getSdbPath();
31 | },
32 | isIpAddress: ip => {
33 | return regExp.IP_ADDRESS.test(ip);
34 | },
35 |
36 | isRemoteUrl: url => {
37 | return regExp.REMOTE_URI.test(url);
38 | },
39 |
40 | isProxy: address => {
41 | return regExp.PROXY.test(address);
42 | },
43 |
44 | getAbsolutePath: inputPath => {
45 | return path
46 | .join(CURRENT_PROJECT_PATH, inputPath)
47 | .replace(regExp.BACKSLASH, '/');
48 | },
49 |
50 | createEmptyFile: (filepath, content) => {
51 | if (content === undefined) {
52 | content = '';
53 | }
54 | try {
55 | fs.accessSync(path.join(filepath));
56 | } catch (e) {
57 | try {
58 | fs.writeFileSync(path.join(filepath), content, 'utf8');
59 | fs.chmodSync(path.join(filepath), '0775');
60 | } catch (error) {
61 | logger.error(
62 | chalk.red(`Failed to createEmptyFile ${filepath} ${error}`)
63 | );
64 | process.exit(0);
65 | }
66 | }
67 | },
68 |
69 | createEmptyDirectory: dirname => {
70 | try {
71 | mkdirp.sync(dirname);
72 | } catch (error) {
73 | logger.error(
74 | chalk.red(`Failed to createEmptyDirectory ${dirname} ${error}`)
75 | );
76 | }
77 | },
78 |
79 | removeFile: filepath => {
80 | if (fs.existsSync(filepath)) {
81 | try {
82 | fs.unlinkSync(filepath);
83 | } catch (e) {
84 | logger.error(chalk.red(`Failed to removeFile ${filepath} ${e}`));
85 | throw e;
86 | }
87 | }
88 | },
89 |
90 | moveFile: (src, dest) => {
91 | try {
92 | if (module.exports.isFileExist(src)) {
93 | module.exports.copyFile(src, dest);
94 | module.exports.removeFile(src);
95 | }
96 | } catch (e) {
97 | logger.error(chalk.red(`Failed to moveFile: ${e}`));
98 | throw e;
99 | }
100 | },
101 |
102 | copyFile: (src, dest) => {
103 | try {
104 | if (module.exports.isFileExist(src)) {
105 | fs.createReadStream(src).pipe(fs.createWriteStream(dest));
106 | }
107 | } catch (e) {
108 | logger.error(chalk.red(`Failed to copyFile: ${e}`));
109 | throw e;
110 | }
111 | },
112 |
113 | setCurrentAppPath: path => {
114 | if (path !== '.') {
115 | module.exports.CURRENT_PROJECT_PATH = path;
116 | }
117 | },
118 |
119 | isFileExist: filePath => {
120 | try {
121 | fs.accessSync(filePath);
122 | return true;
123 | } catch (e) {
124 | return false;
125 | }
126 | },
127 |
128 | isPropertyExist: (data, propertyName) => {
129 | if (
130 | data !== null &&
131 | typeof data === 'object' &&
132 | data.hasOwnProperty(propertyName)
133 | ) {
134 | return true;
135 | }
136 | return false;
137 | },
138 |
139 | clearComment: data => {
140 | return data.replace(regExp.COMMENT, '');
141 | },
142 |
143 | displayOutput: logs => {
144 | if (module.exports.ISVERVOSE === true) {
145 | logger.log(logs);
146 | }
147 | },
148 | getSocketPort: () => {
149 | const REMIND_SOCKET_PORT_LEN = 3;
150 | const MAX_DIGIT = 9;
151 | let port = Math.floor(Math.random() * MAX_DIGIT) + 1 + '';
152 | for (let i = 0; i < REMIND_SOCKET_PORT_LEN; i++) {
153 | port += Math.floor(Math.random() * MAX_DIGIT) + '';
154 | }
155 | return Number(port);
156 | },
157 |
158 | getValidHostIp: (cInfo, answer) => {
159 | let hostIp = ip.address();
160 | if (module.exports.isPropertyExist(cInfo, 'hostIp')) {
161 | hostIp = cInfo.hostIp;
162 | }
163 | if (module.exports.isPropertyExist(answer, 'hostIp')) {
164 | hostIp = answer.hostIp;
165 | }
166 | return hostIp;
167 | },
168 |
169 | getHostIpAddresses: () => {
170 | const networkInterfaces = os.networkInterfaces();
171 | const ipAddresses = [];
172 |
173 | for (var eth in networkInterfaces) {
174 | var interfaces = networkInterfaces[eth];
175 | for (var i = 0; i < interfaces.length; i++) {
176 | var network = interfaces[i];
177 | if (isIpv4Address(network)) {
178 | ipAddresses.push(network.address);
179 | }
180 | }
181 | }
182 | return ipAddresses;
183 | },
184 |
185 | displayBanner() {
186 | logger.log(' _ ____________ ');
187 | logger.log('| | /| / / _/_ __/__');
188 | logger.log('| |/ |/ // / / / (_-<');
189 | logger.log('|__/|__/___/ /_/ /___/\n');
190 | },
191 |
192 | chmodAll(path) {
193 | try {
194 | fs.accessSync(path, fs.constants.S_IXUSR);
195 | } catch (e) {
196 | fs.chmodSync(path, fs.constants.S_IRWXU | fs.constants.S_IRWXG);
197 | }
198 | },
199 |
200 | exit: () => {
201 | process.exit(0);
202 | },
203 |
204 | parseDeviceIp: option => {
205 | if (typeof option !== 'string') {
206 | return null;
207 | }
208 | const deviceIp = option.split('deviceIp=')[1];
209 | if (module.exports.isIpAddress(deviceIp)) {
210 | return deviceIp;
211 | }
212 | return null;
213 | }
214 | };
215 |
216 | function isIpv4Address(eth) {
217 | if (eth.family === 'IPv4' && eth.address !== '127.0.0.1' && !eth.internal) {
218 | return true;
219 | } else {
220 | return false;
221 | }
222 | }
223 |
--------------------------------------------------------------------------------
/lib/appLaunchHelper.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 | const chalk = require('chalk');
3 | const { execSync } = require('child_process');
4 | const chromeLauncher = require('chrome-launcher');
5 | const regExp = require('./regexp.js');
6 | const util = require('./util.js');
7 | const { logger } = require('./logger');
8 |
9 | const PACKAGE_BASE_PATH = path.join(util.WITS_BASE_PATH, '../', 'container');
10 | const WITS_PACKAGE = 'WITs.wgt';
11 | const EMULATOR_IP = '0.0.0.0';
12 |
13 | module.exports = {
14 | installPackage: (deviceInfo, hostAppName) => {
15 | const deviceName = deviceInfo.deviceName;
16 | const appInstallPath = deviceInfo.appInstallPath;
17 | const WGT_FILE_PUSH_COMMAND = `${
18 | util.TOOLS_SDB_PATH
19 | } -s ${deviceName} push "${path.join(
20 | PACKAGE_BASE_PATH,
21 | WITS_PACKAGE
22 | )}" "${appInstallPath}"`;
23 | const APP_INSTALL_COMMAND = `${util.TOOLS_SDB_PATH} -s ${deviceName} shell 0 vd_appinstall ${hostAppName} ${appInstallPath}${WITS_PACKAGE}`;
24 |
25 | const pushResult = execSync(WGT_FILE_PUSH_COMMAND, {
26 | encoding: 'utf-8',
27 | stdio: 'pipe'
28 | });
29 | util.displayOutput(pushResult);
30 |
31 | const installResult = execSync(APP_INSTALL_COMMAND, {
32 | encoding: 'utf-8',
33 | stdio: 'pipe'
34 | });
35 | util.displayOutput(installResult);
36 |
37 | if (installResult.includes('failed[')) {
38 | logger.error(chalk.red(`\nFailed to install Wits`));
39 | util.exit();
40 | }
41 | },
42 | unInstallPackage: (deviceName, hostAppName) => {
43 | const APP_UNINSTALL_COMMAND = `${util.TOOLS_SDB_PATH} -s ${deviceName} shell 0 vd_appuninstall ${hostAppName}`;
44 | const result = execSync(APP_UNINSTALL_COMMAND, {
45 | encoding: 'utf-8',
46 | stdio: 'pipe'
47 | });
48 | util.displayOutput(result);
49 |
50 | if (result.includes('failed[')) {
51 | logger.warn(`\n[warning] Failed to uninstall Wits`);
52 | }
53 | },
54 | launchApp: (deviceName, hostAppId) => {
55 | const APP_LAUNCH_COMMAND = `${util.TOOLS_SDB_PATH} -s ${deviceName} shell 0 was_execute ${hostAppId}`;
56 |
57 | const result = execSync(APP_LAUNCH_COMMAND, {
58 | encoding: 'utf-8',
59 | stdio: 'pipe'
60 | });
61 | util.displayOutput(result);
62 |
63 | if (result === null || result.includes('failed[')) {
64 | throw new Error(
65 | 'Failed to launchApp. Please check the application is already installed on the Target.'
66 | );
67 | }
68 | },
69 | launchDebugMode: (deviceName, hostAppId, deviceIpAddress) => {
70 | const APP_LAUNCH_DEBUG_MODE_COMMAND = `${util.TOOLS_SDB_PATH} -s ${deviceName} shell 0 debug ${hostAppId}`;
71 | const APP_LAUNCH_DEBUG_MODE_COMMAND_TIMEOUT = `${APP_LAUNCH_DEBUG_MODE_COMMAND} 300`;
72 |
73 | const result =
74 | execSync(APP_LAUNCH_DEBUG_MODE_COMMAND, {
75 | encoding: 'utf-8',
76 | stdio: 'pipe'
77 | }) ||
78 | execSync(APP_LAUNCH_DEBUG_MODE_COMMAND_TIMEOUT, {
79 | encoding: 'utf-8',
80 | stdio: 'pipe'
81 | });
82 | util.displayOutput(result);
83 |
84 | if (result === null || result.includes('failed')) {
85 | throw new Error(
86 | 'Failed to launchDebugMode. Please check the application is already installed on the Target.'
87 | );
88 | }
89 |
90 | const debugPort = result
91 | .match(regExp.DEBUG_PORT)[0]
92 | .match(regExp.NUMBER_WORD)[0];
93 | let debugIP = '';
94 | if (deviceIpAddress === EMULATOR_IP) {
95 | const LOCAL_HOST = '127.0.0.1';
96 | setPortForward(deviceName, debugPort);
97 | debugIP = LOCAL_HOST;
98 | } else {
99 | debugIP = deviceIpAddress;
100 | }
101 |
102 | try {
103 | launchChrome(debugIP + ':' + debugPort);
104 | } catch (e) {
105 | logger.log(
106 | `Please install a Chrome browser or input ${debugIP}:${debugPort} into the address bar of the chromium based browser. ${e}`
107 | );
108 | }
109 | },
110 | terminateApp: (deviceName, hostAppId) => {
111 | const APP_TERMINATE_COMMAND = `${util.TOOLS_SDB_PATH} -s ${deviceName} shell 0 was_kill ${hostAppId}`;
112 | const result = execSync(APP_TERMINATE_COMMAND, {
113 | encoding: 'utf-8',
114 | stdio: 'pipe'
115 | });
116 | util.displayOutput(result);
117 | }
118 | };
119 |
120 | function setPortForward(deviceName, port) {
121 | const LOCAL_HOST = '127.0.0.1';
122 | const removeResult = execSync(
123 | `${util.TOOLS_SDB_PATH} -s ${deviceName} forward --remove tcp:${port}`,
124 | {
125 | encoding: 'utf-8',
126 | stdio: 'pipe'
127 | }
128 | );
129 | util.displayOutput(removeResult);
130 |
131 | const tcpResult = execSync(
132 | `${util.TOOLS_SDB_PATH} -s ${deviceName} forward tcp:${port} tcp:${port}`,
133 | {
134 | encoding: 'utf-8',
135 | stdio: 'pipe'
136 | }
137 | );
138 | util.displayOutput(tcpResult);
139 | try {
140 | launchChrome(LOCAL_HOST + ':' + port);
141 | } catch (e) {
142 | logger.log(
143 | `Please install a Chrome browser or input ${LOCAL_HOST}:${port} into the address bar of the chromium based browser. ${e}`
144 | );
145 | }
146 | }
147 |
148 | function launchChrome(url) {
149 | chromeLauncher
150 | .launch({
151 | startingUrl: url,
152 | chromeFlags: [
153 | '--disable-web-security',
154 | '--enable-blink-features=ShadowDOMV0,CustomElementsV0,HTMLImports'
155 | ]
156 | })
157 | .then(chrome => {
158 | logger.log(`Chrome debugging port running on ${chrome.port}`);
159 | })
160 | .catch(e => {
161 | logger.log(chalk.red(`Please install a Chrome browser. ${e}`));
162 | });
163 | }
164 |
--------------------------------------------------------------------------------
/lib/certificationHelper.js:
--------------------------------------------------------------------------------
1 | const inquirer = require('inquirer');
2 | const xml2js = require('xml2js');
3 | const fs = require('fs');
4 | const path = require('path');
5 | const util = require('./util');
6 | const regExp = require('./regexp.js');
7 |
8 | const CERTIFICATION_TYPE = {
9 | tizen: 'tizen',
10 | samsung: 'samsung'
11 | };
12 |
13 | const PRIVILEGE_LEVEL = {
14 | public: 'public',
15 | partner: 'partner'
16 | };
17 |
18 | let profileNames = [];
19 |
20 | module.exports = {
21 | CERTIFICATION_DATA: {},
22 |
23 | getAnswers: async () => {
24 | profiles = await getProfileNames();
25 | const preAnswer = await inquirer.prompt([
26 | getCertificationTypeQuestion()
27 | ]);
28 |
29 | let answer = {};
30 | switch (preAnswer.type) {
31 | case CERTIFICATION_TYPE.tizen:
32 | answer = await getTizenCertificationData();
33 | break;
34 | case CERTIFICATION_TYPE.samsung:
35 | answer = await getSamsungCertificationData();
36 | default:
37 | break;
38 | }
39 | return answer;
40 | }
41 | };
42 |
43 | async function getProfileNames() {
44 | try {
45 | const profilePath = path.join(
46 | util.WITS_BASE_PATH,
47 | '../',
48 | 'resource',
49 | 'profiles.xml'
50 | );
51 | util.isFileExist(profilePath);
52 | const profileFile = fs.readFileSync(profilePath, 'utf8');
53 | const xmlParser = new xml2js.Parser();
54 | const parsedProfiles = await xmlParser.parseStringPromise(profileFile);
55 | if (parsedProfiles.profiles && parsedProfiles.profiles.profile) {
56 | const profiles = parsedProfiles.profiles.profile;
57 | profiles.forEach(item => {
58 | profileNames.push(item.$.name);
59 | });
60 | }
61 | } catch (error) {
62 | profileNames = [];
63 | }
64 | }
65 |
66 | async function getTizenCertificationData() {
67 | const privilegeInfo = [];
68 | privilegeInfo.push(getPrivilegeLevel());
69 | const privilegeAnswer = await inquirer.prompt(privilegeInfo);
70 |
71 | const profileInfo = [];
72 | profileInfo.push(getProfileName());
73 | const profileAnswer = await inquirer.prompt(profileInfo);
74 |
75 | const authorInfo = [];
76 | authorInfo.push(getKeyFileName());
77 | authorInfo.push(getAuthorName());
78 | authorInfo.push(getAuthorPassword());
79 | const authorAnswer = await inquirer.prompt(authorInfo);
80 |
81 | const detailConfirm = await inquirer.prompt(getDetailOptionalQuestion());
82 | const detailInfo = [];
83 | if (detailConfirm.details === true) {
84 | detailInfo.push(getCountryInfo());
85 | detailInfo.push(getStateInfo());
86 | detailInfo.push(getCityInfo());
87 | detailInfo.push(getOrganizationInfo());
88 | detailInfo.push(getDepartmentInfo());
89 | detailInfo.push(getEmailInfo());
90 | }
91 | const detailAnswer = await inquirer.prompt(detailInfo);
92 |
93 | return Object.assign(
94 | privilegeAnswer,
95 | profileAnswer,
96 | authorAnswer,
97 | detailAnswer
98 | );
99 | }
100 |
101 | function getSamsungCertificationData() {
102 | let ask = [];
103 | ask.push();
104 | return ask;
105 | }
106 |
107 | function getCertificationTypeQuestion() {
108 | return {
109 | type: 'list',
110 | name: 'type',
111 | message: 'Select certification type : ',
112 | choices: [CERTIFICATION_TYPE.tizen],
113 | // choices: [CERTIFICATION_TYPE.tizen, CERTIFICATION_TYPE.samsung],
114 | default: CERTIFICATION_TYPE.tizen
115 | };
116 | }
117 |
118 | function getDetailOptionalQuestion() {
119 | return {
120 | type: 'confirm',
121 | name: 'details',
122 | message: 'Do you want to fill in more details? (Optional)',
123 | default: false
124 | };
125 | }
126 |
127 | function getProfileName() {
128 | return {
129 | type: 'input',
130 | name: 'profileName',
131 | message: 'Input an unique "profileName" : ',
132 | validate: input => {
133 | return input !== ''
134 | ? (profileNames && profileNames.indexOf(input)) === -1
135 | ? true
136 | : 'The name is alreaday in use.'
137 | : 'Input an unique name';
138 | }
139 | };
140 | }
141 |
142 | function getKeyFileName() {
143 | return {
144 | type: 'input',
145 | name: 'keyFileName',
146 | message: 'Input keyFileName : ',
147 | validate: input => {
148 | return input !== ''
149 | ? !input.match(regExp.CONTAIN_WHITESPACE)
150 | ? true
151 | : 'Input without whitespace'
152 | : 'Invalid value';
153 | }
154 | };
155 | }
156 |
157 | function getAuthorName() {
158 | return {
159 | type: 'input',
160 | name: 'authorName',
161 | message: 'Input an authorName : ',
162 | validate: input => {
163 | return input !== ''
164 | ? !input.match(regExp.CONTAIN_WHITESPACE)
165 | ? true
166 | : 'Input without whitespace'
167 | : 'Invalid value';
168 | }
169 | };
170 | }
171 |
172 | function getAuthorPassword() {
173 | return {
174 | type: 'password',
175 | name: 'authorPassword',
176 | mask: true,
177 | message: 'Input a password for author certification : ',
178 | validate: input => {
179 | const MIN_LEN = 8;
180 | return input.length >= MIN_LEN ? true : 'Input over 8 characters';
181 | }
182 | };
183 | }
184 |
185 | function getCountryInfo() {
186 | return {
187 | type: 'input',
188 | name: 'countryInfo',
189 | message: 'Input countryInfo : '
190 | };
191 | }
192 |
193 | function getStateInfo() {
194 | return {
195 | type: 'input',
196 | name: 'stateInfo',
197 | message: 'Input stateInfo : '
198 | };
199 | }
200 |
201 | function getCityInfo() {
202 | return {
203 | type: 'input',
204 | name: 'cityInfo',
205 | message: 'Input cityInfo : '
206 | };
207 | }
208 |
209 | function getOrganizationInfo() {
210 | return {
211 | type: 'input',
212 | name: 'organizationInfo',
213 | message: 'Input organizationInfo : '
214 | };
215 | }
216 |
217 | function getDepartmentInfo() {
218 | return {
219 | type: 'input',
220 | name: 'departmentInfo',
221 | message: 'Input departmentInfo : '
222 | };
223 | }
224 |
225 | function getEmailInfo() {
226 | return {
227 | type: 'input',
228 | name: 'emailInfo',
229 | message: 'Input emailInfo : '
230 | };
231 | }
232 |
233 | function getPrivilegeLevel() {
234 | return {
235 | type: 'list',
236 | name: 'privilegeLevel',
237 | message: 'Select privilege Level : ',
238 | // choices: [PRIVILEGE_LEVEL.public, PRIVILEGE_LEVEL.partner],
239 | choices: [PRIVILEGE_LEVEL.public],
240 | default: PRIVILEGE_LEVEL.public
241 | };
242 | }
243 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # WITs
2 |
3 | WITs is a useful development tool for helping to run and develop your Tizen web application easily on your **2017+ Samsung TV**.
4 | It is the fastest way to get your local code running on the TV device during development. Saving you from having to build, package, and reinstall your application every time you want to see how it will run on device. We call it a `LIVE RELOAD`.
5 | **WITs is helpful to continue your developing context.**
6 |
7 |
8 |
9 | ## Supported WITs guide in other languages
10 |
11 | - [简体中文](doc/README_zh_HANS.md)
12 | - [正體中文](doc/README_zh_HANT.md)
13 |
14 | Always welcome, if you contribute WITs guide in your language!
15 | Guides should be placed in "doc" directory.
16 | Please pull-request and join it!
17 |
18 | ## Installing and Configuring WITs
19 |
20 | ### For developers using npm
21 |
22 | #### 1. Install WITs npm globally
23 |
24 | ```sh
25 | $ npm install -g @tizentv/wits
26 | ```
27 |
28 | ### For developers using Git repository
29 |
30 | #### 1. Clone WITs git repository.
31 |
32 | ```sh
33 | $ git clone https://github.com/Samsung/Wits.git
34 | ```
35 |
36 | #### 2. Install WITs Dependencies
37 |
38 | ```sh
39 | $ cd ~/{path-to}/Wits
40 | $ npm install -g
41 | ```
42 |
43 | ## **System Requirements**
44 |
45 | WITs needs the following prerequisites on your local development machine.
46 |
47 | #### 1. Open **`Terminal` on MacOS / Linux** or **`CMD` / `PowerShell` on Windows**
48 |
49 | #### 2. Install Node.js and Git (recommend v7.10.1 ~)
50 |
51 | We will not describe how to do these installations as there are many ways to do it and its developer preference. We recommend using something like `nvm` or `asdf` to manage different versions of Node.js across your code projects.
52 |
53 | #### 3. Developer Mode is enabled on your Samsung TV.
54 |
55 | - 1 With your Samsung Remote, press the `Home` button.
56 |
57 | - 2 Navigate to the `Apps` button and press `Enter/OK`.
58 |
59 | - 3 When on the `Apps` screen, press `1` `2` `3` `4` `5` in order on the remote to open the `Developer Mode Dialog`. If this doesn't work, try it again.
60 |
61 | - 4 When the Developer Mode Dialog appears, toggle the switch to `On` and enter the IP address of your development machine.
62 |
63 | #### 4. Certification for packaging application (Tizen / Samsung)
64 |
65 | Certification(Tizen / Samsung) is required for packaging your tizen web application.
66 |
67 | `Using Editor`
68 |
69 | - Tizen Studio
70 | Install the latest version of [Tizen Studio](http://developer.samsung.com/tv).
71 |
72 | - VSCode
73 | Install the latest version of [VSCode](https://code.visualstudio.com/).
74 | And download the extension "tizensdk.tizentv".
75 |
76 | - Atom
77 | Install the latest version of [Atom](https://atom.io/).
78 | And download the package "atom-tizentv-2"
79 |
80 | `Using WITs`
81 |
82 | - WITs (v2.4.0 ~) supports creating a Tizen certification.
83 | Please do "wits -c" for making a new Tizen certification.
84 |
85 | ## WITs details
86 |
87 | ### The Project Structure for using WITs
88 |
89 | .witsconfig.json, .witsignore files are only added at the your tizen web application.
90 |
91 | ### WITs CLI
92 |
93 | #### `wits`
94 |
95 | For showing which options you can use
96 |
97 | #### `wits -i` / `wits --init`
98 |
99 | For configuring WITs
100 | Please note that, It should be run when you use first time on your tizen application project.
101 | .witsconfig.json and .witsignore files are generated on your tizen app project.
102 | After then, you can modify your information to them.
103 |
104 | 
105 |
106 | #### `wits -c` / `wits --certificate`
107 |
108 | For creating a certification(Supported Tizen certification only).
109 | As following steps, you can create a certification on `~/{path-to}/wits/resource/profiles.xml`.
110 |
111 | 
112 |
113 | #### `wits -s` / `wits --start`
114 |
115 | All in one. For connecting to TV, installing and launching your app and using Live Reload
116 | If `wits -i` hasn't run before, It is not allowed to run.
117 |
118 | ```sh
119 | # Run wits --start
120 | $ wits -s
121 |
122 | # Run wits --start with deviceIp. Available to switch the device Ip easily.
123 | $ wits -s deviceIp=192.168.250.250
124 |
125 | # Run wits --start with deviceIp. Available to switch the device Ip easily. For debugging, add --verbose option. It should be at the end of command.
126 | $ wits -s deviceIp=192.168.250.250 --verbose
127 | ```
128 |
129 | 
130 |
131 | #### `wits -w` / `wits --watch`
132 |
133 | For connecting to TV, using Live Reload
134 | After connecting, every time you make changes on `your tizen app project`, It is reflected to TV device instantly.
135 |
136 | ```sh
137 | # Run wits --watch
138 | $ wits -w
139 |
140 | # Run wits --watch with deviceIp. Available to switch the device Ip easily.
141 | $ wits -w deviceIp=192.168.250.250
142 |
143 | # Run wits --watch with deviceIp. Available to switch the device Ip easily. For debugging, add --verbose option. It should be at the end of command.
144 | $ wits -w deviceIp=192.168.250.250 --verbose
145 | ```
146 |
147 | 
148 |
149 | ### WITs API
150 |
151 | WITs supports the following APIs
152 |
153 | - [setWitsconfigInfo(WitsInfoData data)](https://github.com/Samsung/Wits/wiki/How-to-use-WITs-as-APIs#setwitsconfiginfo) : This API is for setting WITs environment, It should be called before start function or watch function.
154 | - [start()](https://github.com/Samsung/Wits/wiki/How-to-use-WITs-as-APIs#start) : This API is a sequence for building and installing your application, connecting PC and Target TV, pushing files, supporting live-reload feature.
155 | - [watch()](https://github.com/Samsung/Wits/wiki/How-to-use-WITs-as-APIs#watch) : This API is a sequence for connecting PC and Target TV, pushing files, supporting live-reload feature. (Except for re-building and re-installing your application.)
156 | - [disconnect()](https://github.com/Samsung/Wits/wiki/How-to-use-WITs-as-APIs#disconnect) : This API is for disconnecting communications between PC and Target TV.
157 | - [setOutputChannel(OutputCallback callback)](https://github.com/Samsung/Wits/wiki/How-to-use-WITs-as-APIs#setoutputchannel) : This is for getting WITs' log information. The return value is integer value between 1000 and 9999, and this value will be used to unsetOutputChannel().
158 | - [unsetOutputChannel()](https://github.com/Samsung/Wits/wiki/How-to-use-WITs-as-APIs#unsetoutputchannel) : This is for unregistering the output callback. Call the unsetOutputChannel() with the return value of the setOutputChannel().
159 |
160 | For detail, check whole APIs in [How to use WITs as APIs](https://github.com/Samsung/Wits/wiki/How-to-use-WITs-as-APIs).
161 |
162 | ### .witsconfig.json of WITs
163 |
164 | For details, [Sample data for .witsconfig.json](https://github.com/Samsung/Wits/wiki/Set-Wits-Environment#data-structure-of-witsconfigjson)
165 | on `Windows` and `MacOS` both, **WITs** recognises path segment only one separator(**`/`**).
166 |
167 | - **connectionInfo** (mandatory)
168 | - deviceIp [string] : Device(TV) Ip address (In case of Emulator, Please input 0.0.0.0)
169 | - hostIp [string] : Host(PC) Ip address
170 | - width [string] : Resolution
171 | - isDebugMode [boolean] : Setting true, chrome inspector is launched automatically. / Setting false, nothing happened.
172 | - **profileInfo** (mandatory)
173 | - path [string] : Tizen Studio Certificate Profile path
174 |
175 | ### .witsignore of WITs
176 |
177 | Sometimes there are a few files what you do not want to push to your TV device such as `.git` or `node_modules`.
178 | If you input unnecessary files or directories on .witsignore file before pushing files to the TV device, It would be pushed except them to your TV.
179 | You can use it optionally.
180 | This works exactly same as `.gitignore`.
181 |
182 | Example of `.witsignore`:
183 |
184 | ```text
185 | node_modules
186 | .git
187 | deprecated
188 | stglib
189 | ```
190 |
191 | ## Known Issues
192 |
193 | ### failed to live reload on react application
194 |
195 | - Check the router what you uses, and change to MemoryRouter.
196 | Please refer the closed [Issue #114](https://github.com/Samsung/Wits/issues/114)
197 |
198 | ## FAQ
199 |
200 | - [WITs FAQ](https://github.com/Samsung/Wits/wiki/Frequently-Asked-Questions)
201 | - [How to use debug mode on WITs with Chrome inspector](https://github.com/Samsung/Wits/wiki/Frequently-Asked-Questions#answer-1)
202 | - [How to get your TV IP Address](https://github.com/Samsung/Wits/wiki/Frequently-Asked-Questions#question-7)
203 |
204 | ## Supported Platforms
205 |
206 | - 2017 Samsung Smart TV (Tizen 3.0)
207 | - 2018 Samsung Smart TV (Tizen 4.0)
208 | - 2019 Samsung Smart TV (Tizen 5.0)
209 | - 2020 Samsung Smart TV (Tizen 5.5)
210 |
--------------------------------------------------------------------------------
/container/js/base.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2018 Samsung Electronics Co., Ltd.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | 'use strict';
18 |
19 | (function () {
20 | var iframeElem = null;
21 | var loadingElem = null;
22 | var pieSliceLeftElem = null;
23 | var pieSliceRightElem = null;
24 | var connectIconElem = null;
25 | var connectMessageElem = null;
26 | var socket = null;
27 | var iconDimTimer = null;
28 | var isLoadingContents = false;
29 | var CONTENT_PATH = '{{CONTENT_PATH}}';
30 | var CONTENT_SRC = '{{CONTENT_SRC}}';
31 | var IP = '{{HOST_IP}}';
32 | var PORT = '{{HOST_PORT}}';
33 | var CONNECTED = 'Connected';
34 | var DISCONNECTED = 'Disconnected';
35 |
36 | window.onload = function () {
37 | console.log('onload!!!');
38 | var CONNECTION_WAIT_TIME = 500;
39 | iframeElem = document.getElementById('contentHTML');
40 | loadingElem = document.getElementById('loading');
41 | connectIconElem = document.getElementById('connectIcon');
42 | connectMessageElem = document.getElementById('connectMessage');
43 | pieSliceLeftElem = document.getElementById('pieSliceLeft');
44 | pieSliceRightElem = document.getElementById('pieSliceRight');
45 | toggleConnectInfo(DISCONNECTED);
46 | runIconDimAnimation();
47 |
48 | if (isLaunchFromCommand()) {
49 | tizen.filesystem.resolve(
50 | CONTENT_PATH,
51 | function (path) {
52 | path.parent.deleteDirectory(
53 | path.fullPath,
54 | true,
55 | function () {
56 | console.log('Directory Deleted');
57 | connectPC();
58 | },
59 | function (e) {
60 | console.log(
61 | '[Warning]: Failed to delete directory ' + CONTENT_PATH
62 | );
63 | connectPC();
64 | }
65 | );
66 | },
67 | function (e) {
68 | console.log('[Warning]: Failed to resolved ' + CONTENT_PATH);
69 | connectPC();
70 | },
71 | 'rw'
72 | );
73 | } else {
74 | tizen.filesystem.resolve(
75 | CONTENT_PATH,
76 | function (path) {
77 | loadingElem.innerHTML = 'loading : 100%';
78 | loadingElem.style.width = '100%';
79 | stopIconDimAnimation();
80 | connectPC();
81 | setTimeout(function () {
82 | loadContent(CONTENT_SRC);
83 | }, CONNECTION_WAIT_TIME);
84 | },
85 | function (e) {
86 | alert('Failed to resolve Content Application');
87 | tizen.application.getCurrentApplication().exit();
88 | },
89 | 'r'
90 | );
91 | }
92 | };
93 |
94 | function loadContent(contentSrc) {
95 | var CONTENT_LOAD_WAIT_TIME = 1000;
96 | try {
97 | setTimeout(function () {
98 | iframeElem.src = contentSrc;
99 | iframeElem.style.display = 'block';
100 | iframeElem.onload = function () {
101 | iframeElem.focus();
102 | hideWitsContainer();
103 | };
104 | }, CONTENT_LOAD_WAIT_TIME);
105 | } catch (e) {
106 | console.log('Failed to load content', e);
107 | }
108 | }
109 |
110 | function reloadContent() {
111 | try {
112 | iframeElem.contentDocument.location.reload(true);
113 | iframeElem.style.display = 'block';
114 | iframeElem.focus();
115 | } catch (e) {
116 | console.log('Failed to reload content', e);
117 | }
118 | }
119 |
120 | function toggleConnectInfo(status) {
121 | if (status === CONNECTED) {
122 | connectIconElem.className = 'connected';
123 | connectMessageElem.innerHTML = CONNECTED;
124 | } else {
125 | connectIconElem.className = 'disconnected';
126 | connectMessageElem.innerHTML = DISCONNECTED;
127 | }
128 | }
129 |
130 | function runIconDimAnimation() {
131 | var ANIMATION_DURATION_TIME = 3000;
132 | var ANIMATION_PAUSE_TIME = 500;
133 |
134 | pieSliceLeftElem.className = 'slice-left slice-wrap';
135 | pieSliceRightElem.className = 'slice-right slice-wrap';
136 | iconDimTimer = setInterval(function () {
137 | pieSliceLeftElem.className = '';
138 | pieSliceRightElem.className = '';
139 | if (iconDimTimer) {
140 | setTimeout(function () {
141 | pieSliceLeftElem.className = 'slice-left slice-wrap';
142 | pieSliceRightElem.className = 'slice-right slice-wrap';
143 | }, ANIMATION_PAUSE_TIME);
144 | }
145 | }, ANIMATION_DURATION_TIME);
146 | }
147 |
148 | function stopIconDimAnimation() {
149 | if (iconDimTimer) {
150 | clearInterval(iconDimTimer);
151 | iconDimTimer = null;
152 | }
153 | }
154 | function hideWitsContainer() {
155 | var witsContainerElem = document.getElementById('witsContainer');
156 | witsContainerElem.style.display = 'none';
157 | }
158 |
159 | function isLaunchFromCommand() {
160 | var reqAppControl = tizen.application
161 | .getCurrentApplication()
162 | .getRequestedAppControl();
163 |
164 | var isFromCommand = true;
165 |
166 | if (reqAppControl.appControl.data) {
167 | console.log('reqAppControl.appControl.data');
168 | console.log(reqAppControl.appControl.data);
169 | var launchData = reqAppControl.appControl.data;
170 | launchData.forEach(function (item) {
171 | if (item.key === 'callerid') {
172 | isFromCommand = false;
173 | }
174 | });
175 | }
176 | return isFromCommand;
177 | }
178 |
179 | function connectPC() {
180 | var url = IP + ':' + PORT;
181 | var options = {
182 | reconnection: false,
183 | reconnectionAttempts: 2,
184 | reconnectionDelay: 1000,
185 | reconnectionDelayMax: 5000,
186 | timeout: 5000,
187 | autoConnect: false
188 | };
189 |
190 | socket = io(url, options);
191 |
192 | socket.on('connect_error', function (err) {
193 | alert(`Connect Error(${url}): ${err.message}`);
194 | tizen.application.getCurrentApplication().exit();
195 | });
196 |
197 | socket.on('response', function (chunk) {
198 | console.log('socket on::::response', chunk);
199 | if (chunk.rsp.status === 'connected') {
200 | toggleConnectInfo(CONNECTED);
201 | if (isLaunchFromCommand()) {
202 | socket.emit('push_request');
203 | }
204 | socket.emit('watch_request', {
205 | destPath: CONTENT_PATH
206 | });
207 | }
208 | });
209 |
210 | socket.on('disconnect', function () {
211 | console.log(' disconnect, id = ' + socket.id);
212 | toggleConnectInfo(DISCONNECTED);
213 | socket.disconnect(true);
214 | socket.close();
215 | if (isLoadingContents) {
216 | alert('Failed to load Content Application');
217 | tizen.application.getCurrentApplication().exit();
218 | }
219 | });
220 |
221 | socket.on('push_progress', function (info) {
222 | console.log('socket on::::push_progress');
223 | isLoadingContents = true;
224 | loadingElem.innerHTML =
225 | 'loading : ' +
226 | info.progressRate +
227 | ' (' +
228 | info.load +
229 | '/' +
230 | info.total +
231 | ')';
232 | loadingElem.style.width = info.progressRate;
233 | });
234 |
235 | socket.on('push_completed', function () {
236 | console.log('socket on::::push_completed');
237 | stopIconDimAnimation();
238 | loadContent(CONTENT_SRC);
239 | isLoadingContents = false;
240 | });
241 |
242 | socket.on('push_failed', function () {
243 | console.log('socket on::::push_failed');
244 | alert('Failed to load Content Application');
245 | tizen.application.getCurrentApplication().exit();
246 | });
247 |
248 | socket.on('changed', function () {
249 | reloadContent();
250 | });
251 |
252 | socket.on('remove', function (path) {
253 | tizen.filesystem.resolve(
254 | path,
255 | function (data) {
256 | if (data.isDirectory) {
257 | data.parent.deleteDirectory(
258 | data.fullPath,
259 | true,
260 | function () {
261 | console.log('Directory Deleted');
262 | reloadContent();
263 | },
264 | function (e) {
265 | console.log('Error to Delete Directory.' + e.message);
266 | }
267 | );
268 | } else {
269 | data.parent.deleteFile(
270 | data.fullPath,
271 | function () {
272 | console.log('file Deleted');
273 | reloadContent();
274 | },
275 | function (e) {
276 | console.log('Error to Delete file.' + e.message);
277 | }
278 | );
279 | }
280 | },
281 | function (e) {
282 | console.log('Error: ' + e.message);
283 | },
284 | 'rw'
285 | );
286 | });
287 | socket.open();
288 | }
289 | })();
290 |
--------------------------------------------------------------------------------
/lib/watchHelper.js:
--------------------------------------------------------------------------------
1 | const fs = require('fs');
2 | const path = require('path');
3 | const chalk = require('chalk');
4 | const { exec } = require('child_process');
5 | const watch = require('node-watch');
6 | const recursiveReadDir = require('recursive-readdir');
7 | const express = require('express');
8 | const app = express();
9 | const http = require('http').createServer(app);
10 | const io = require('socket.io')(http);
11 | const htmlParser = require('node-html-parser');
12 |
13 | const regExp = require('./regexp.js');
14 | const appLaunchHelper = require('./appLaunchHelper.js');
15 | const hostAppHelper = require('./hostAppHelper.js');
16 | const util = require('./util.js');
17 | const { logger } = require('./logger');
18 |
19 | const WATCHER_EVENT_UPDATE = 'update';
20 | const WATCHER_EVENT_REMOVE = 'remove';
21 | const WITs_IGNORE_FILE = '.witsignore';
22 | let watcher = {};
23 | let mediator = {};
24 | let witsIgnores = [];
25 | let deviceName = '';
26 |
27 | module.exports = {
28 | openSocketServer: async (data, deviceInfo) => {
29 | const socketPort = data.socketPort;
30 | const hostAppId = hostAppHelper.getHostAppId(data.baseAppPath);
31 | const hostAppName = hostAppId.split('.')[1];
32 | deviceName = deviceInfo.deviceName; //global
33 | const hostAppPath = deviceInfo.appInstallPath + hostAppName;
34 | module.exports.closeSocketServer();
35 | appLaunchHelper.terminateApp(deviceName, hostAppId);
36 |
37 | http.listen(socketPort, data.hostIp, () => {
38 | logger.log(`listening on ${socketPort}`);
39 | });
40 |
41 | http.on('close', () => {
42 | logger.log(chalk.cyanBright(`Close listening: ${socketPort}`));
43 | });
44 |
45 | mediator = io.on('connection', socket => {
46 | logger.log(`a user connected`);
47 | logger.log(`new client connected, id = ${socket.id} `);
48 |
49 | socket.emit('response', {
50 | rsp: {
51 | status: 'connected'
52 | }
53 | });
54 |
55 | socket.on('disconnect', () => {
56 | logger.log(`disconnect, id = ${socket.id}`);
57 | socket.disconnect(true);
58 | watcher.close();
59 | });
60 |
61 | socket.once('connect_error', () => {
62 | logger.log(`socket once::::connect_error`);
63 | });
64 |
65 | socket.on('push_request', () => {
66 | logger.log(`socket on::::push_request`);
67 | startPushFiles(data.baseAppPath, hostAppPath);
68 | });
69 |
70 | socket.on('watch_request', path => {
71 | logger.log(`socket on::::watch_request`);
72 | watchAppCode(data.baseAppPath, path.destPath);
73 | });
74 | });
75 | },
76 | closeSocketServer: () => {
77 | watcher.close && watcher.close();
78 | http.close();
79 | io.removeAllListeners('connection');
80 | io.close();
81 | }
82 | };
83 |
84 | function ignoreFunc(file, stats) {
85 | return witsIgnores.includes(path.basename(file));
86 | }
87 |
88 | function getWitsIgnore(baseAppPath) {
89 | const file = path.resolve(path.join(baseAppPath, WITs_IGNORE_FILE));
90 | let ignore = [];
91 |
92 | try {
93 | let ignoreData = fs.readFileSync(file, 'utf8').trim();
94 | if (ignoreData && ignoreData.length > 0) {
95 | ignore = ignoreData.replace(regExp.FIND_ALL_CR, '').split('\n');
96 | }
97 | } catch (e) {
98 | logger.warn(`[warning] Failed to get Wits ignore ${e}`);
99 | }
100 | return ignore;
101 | }
102 |
103 | async function getContentFiles(baseAppPath) {
104 | let data = [];
105 | try {
106 | data = await recursiveReadDir(baseAppPath, [ignoreFunc]);
107 | } catch (e) {
108 | logger.error(chalk.red(`Failed to get content files`));
109 | util.exit();
110 | return;
111 | }
112 |
113 | return data;
114 | }
115 |
116 | function updatePushProgress(currentNumber, totalNumber) {
117 | if (currentNumber > totalNumber) {
118 | currentNumber = totalNumber;
119 | }
120 |
121 | mediator.emit('push_progress', {
122 | total: totalNumber,
123 | load: currentNumber,
124 | progressRate: Math.floor((currentNumber / totalNumber) * 100) + '%'
125 | });
126 | }
127 |
128 | function startPushFiles(baseAppPath, hostAppPath) {
129 | const START_PUSH_INDEX = 0;
130 | let totalFileNum = 0;
131 |
132 | witsIgnores = getWitsIgnore(baseAppPath);
133 |
134 | getContentFiles(baseAppPath).then(files => {
135 | totalFileNum = files.length;
136 | logger.log(`Total File Number : ${totalFileNum}`);
137 | const contentFilesInfo = {
138 | files: files,
139 | curIdx: START_PUSH_INDEX,
140 | totalFileNum: totalFileNum
141 | };
142 | pushFile(baseAppPath, hostAppPath, contentFilesInfo);
143 | });
144 | }
145 |
146 | function pushFile(baseAppPath, hostAppPath, filesInfo) {
147 | if (filesInfo.curIdx >= filesInfo.totalFileNum) {
148 | mediator.emit('push_completed');
149 | } else {
150 | const file = filesInfo.files[filesInfo.curIdx];
151 | let filePath = path.isAbsolute(file)
152 | ? file.replace(regExp.BACKSLASH, '/')
153 | : util.getAbsolutePath(file);
154 | const fileName = filePath.replace(baseAppPath, '');
155 | const contentSrc = getContentSrc(baseAppPath);
156 | if (
157 | !util.isRemoteUrl(contentSrc) &&
158 | contentSrc === fileName.replace(regExp.FIRST_BACKSLASH, '')
159 | ) {
160 | try {
161 | pushFsWrapperFile(hostAppPath);
162 | filePath = getWrappedContentFiles(filePath, fileName);
163 | } catch (e) {
164 | logger.log('[Warning]: Failed to wrapped FileSystem to contents file.');
165 | }
166 | }
167 | const CONTENT_FILE_PUSH_COMMAND = `${util.TOOLS_SDB_PATH} -s ${deviceName} push "${filePath}" "${hostAppPath}${fileName}"`;
168 | const pushResult = exec(CONTENT_FILE_PUSH_COMMAND, {
169 | async: true,
170 | encoding: 'utf-8',
171 | stdio: 'pipe'
172 | });
173 | // util.displayOutput(pushResult);
174 |
175 | pushResult.stderr.on('data', data => {
176 | const COMPATIBILITY_ERROR = 'version compatibility problems';
177 | if (!data.includes(COMPATIBILITY_ERROR)) {
178 | mediator.emit('push_failed');
179 | }
180 | });
181 | pushResult.stdout.on('data', data => {
182 | if (regExp.PUSHED_FILE_MESSAGE.test(data)) {
183 | ++filesInfo.curIdx;
184 | updatePushProgress(filesInfo.curIdx, filesInfo.totalFileNum);
185 | pushFile(baseAppPath, hostAppPath, filesInfo);
186 | }
187 | });
188 | }
189 | }
190 |
191 | function watchAppCode(basePath, destPath) {
192 | watcher = watch(basePath, { recursive: true }, (evt, name) => {
193 | logger.log(`${name} ${evt}`);
194 | const filePath = name.replace(regExp.BACKSLASH, '/').replace(basePath, '');
195 | logger.log(`watch file : ${filePath}`);
196 | if (!isIgnore(filePath)) {
197 | if (evt === WATCHER_EVENT_UPDATE) {
198 | pushUpdated(basePath, destPath, filePath);
199 | } else if (evt === WATCHER_EVENT_REMOVE) {
200 | emitRemoved(basePath, destPath, filePath);
201 | }
202 | }
203 | });
204 | }
205 |
206 | function isIgnore(path) {
207 | let isIgnore = false;
208 | witsIgnores.some(ignore => {
209 | if (path.includes(ignore)) {
210 | logger.log(`This watch file is ignore.`);
211 | isIgnore = true;
212 | return true;
213 | }
214 | });
215 | return isIgnore;
216 | }
217 |
218 | function pushUpdated(basePath, destPath, filePath) {
219 | const contentSrc = getContentSrc(basePath);
220 | let fileName = filePath.replace(regExp.FIRST_BACKSLASH, '');
221 | let fileFullPath = basePath + filePath;
222 | if (!util.isRemoteUrl(contentSrc) && contentSrc === fileName) {
223 | try {
224 | fileFullPath = getWrappedContentFiles(fileFullPath, fileName);
225 | } catch (e) {
226 | logger.log('[Warning]: Failed to wrapped FileSystem to contents file.');
227 | }
228 | }
229 | const UPDATE_FILE_PUSH_COMMAND = `${util.TOOLS_SDB_PATH} -s ${deviceName} push "${fileFullPath}" "${destPath}${filePath}"`;
230 | const result = exec(
231 | UPDATE_FILE_PUSH_COMMAND,
232 | { encoding: 'utf-8', stdio: 'pipe' },
233 | (code, stdout, stderr) => {
234 | if (stderr) {
235 | const COMPATIBILITY_ERROR = 'version compatibility problems';
236 | if (!stderr.includes(COMPATIBILITY_ERROR)) {
237 | logger.log(`Failed ${stderr}`);
238 | util.exit();
239 | }
240 | }
241 | logger.log(`Program output : ${stdout}`);
242 | if (stdout.includes('file(s) pushed')) {
243 | mediator.emit('changed');
244 | }
245 | }
246 | );
247 |
248 | // util.displayOutput(result);
249 | }
250 |
251 | function emitRemoved(basePath, destPath, filePath) {
252 | logger.log(filePath);
253 | mediator.emit(WATCHER_EVENT_REMOVE, destPath + filePath);
254 | }
255 |
256 | function getContentSrc(baseAppPath) {
257 | let contentSrc = 'index.html';
258 |
259 | try {
260 | const file = path.resolve(path.join(baseAppPath, 'config.xml'));
261 | let data = fs.readFileSync(file, 'utf8');
262 | data = util.clearComment(data);
263 | contentSrc = data
264 | .match(regExp.CONTENT_SRC)[0]
265 | .replace(regExp.CONTENT_SRC_ATTRIBUTE, '');
266 | } catch (e) {
267 | logger.warn(
268 | `[warning] Failed to read config.xml. Set Content src to default.`
269 | );
270 | }
271 | return contentSrc.replace(regExp.FIRST_BACKSLASH, '');
272 | }
273 |
274 | function pushFsWrapperFile(hostAppPath) {
275 | const WRAPPER_FILE = 'wrapper/filesystemWrapper.js';
276 | const WRAPPER_FILE_PATH = path
277 | .join(util.WITS_BASE_PATH, WRAPPER_FILE)
278 | .replace(regExp.BACKSLASH, '/');
279 | const WRAPPER_FILE_PUSH_COMMAND = `${util.TOOLS_SDB_PATH} -s ${deviceName} push "${WRAPPER_FILE_PATH}" "${hostAppPath}/${WRAPPER_FILE}"`;
280 | const result = exec(WRAPPER_FILE_PUSH_COMMAND, {
281 | async: true,
282 | encoding: 'utf-8',
283 | stdio: 'pipe'
284 | });
285 | // util.displayOutput(result);
286 | }
287 |
288 | function getWrappedContentFiles(filePath, fileName) {
289 | let fileData = fs.readFileSync(filePath, 'utf8');
290 | let newFileData = appendFsWrapperScript(fileData);
291 | let newFilePath = path.join(util.WITS_BASE_PATH, 'wrapper', fileName);
292 | fs.writeFileSync(newFilePath, newFileData, 'utf8');
293 | return newFilePath;
294 | }
295 |
296 | function appendFsWrapperScript(fileData) {
297 | const root = htmlParser.parse(fileData);
298 | root
299 | .querySelector('head')
300 | .appendChild(
301 | '\n'
302 | );
303 | return root.toString();
304 | }
305 |
--------------------------------------------------------------------------------
/lib/hostAppHelper.js:
--------------------------------------------------------------------------------
1 | const fs = require('fs');
2 | const path = require('path');
3 | const xml2js = require('xml2js');
4 | const chalk = require('chalk');
5 | const common = require('@tizentv/webide-common-tizentv');
6 |
7 | const util = require('./util.js');
8 | const userInfoHelper = require('./userInfoHelper.js');
9 | const regExp = require('./regexp.js');
10 | const { logger } = require('./logger');
11 |
12 | const CONFIG_FILE = 'config.xml';
13 | const WITS_NAME_TAG = 'WITs';
14 |
15 | module.exports = {
16 | setHostAppEnv: async (userAnswer, deviceInfo) => {
17 | await makeHostAppConfigFile(userAnswer.baseAppPath);
18 | setBaseJSData(userAnswer, deviceInfo);
19 | setBaseFsWrapper(userAnswer, deviceInfo);
20 | setBaseHtmlData(userAnswer);
21 | },
22 | getHostAppId: baseAppPath => {
23 | try {
24 | const file = baseAppPath + '/' + CONFIG_FILE;
25 | let data = fs.readFileSync(file, 'utf8');
26 | data = util.clearComment(data);
27 | const id = data
28 | .match(regExp.APPLICATION_ID)[0]
29 | .replace(regExp.APPLICATION_ID_ATTRIBUTE, '');
30 | return id + WITS_NAME_TAG;
31 | } catch (e) {
32 | logger.error(chalk.red(`Failed to read base app config.xml. ${e}`));
33 | util.exit();
34 | }
35 | },
36 | buildPackage: async () => {
37 | const buildPath = path.join(util.WITS_BASE_PATH, '../', 'container');
38 | const profilePath = userInfoHelper.getLatestWitsconfigInfo().profileInfo
39 | .path;
40 | const hostAppId = module.exports.getHostAppId(
41 | util.CURRENT_PROJECT_PATH
42 | );
43 | try {
44 | logger.log(
45 | chalk.cyanBright(
46 | `\nStart packaging Samsung Tizen TV Platform......\n`
47 | )
48 | );
49 | const app = new common.TVWebApp(
50 | WITS_NAME_TAG,
51 | buildPath,
52 | hostAppId
53 | );
54 | if (!util.isFileExist(profilePath)) {
55 | logger.error(
56 | chalk.red(
57 | `Please check ${profilePath} is valid. For making a new certification, please do "wits -c"`
58 | )
59 | );
60 | util.exit();
61 | }
62 | await app.buildWidget(profilePath);
63 |
64 | logger.log(
65 | chalk.cyanBright(
66 | '============================== Build Package completed!'
67 | )
68 | );
69 | logger.log('');
70 | return;
71 | } catch (error) {
72 | logger.log('Failed to buildPackage : ' + error);
73 | }
74 | }
75 | };
76 |
77 | async function makeHostAppConfigFile(baseAppPath) {
78 | let userConfigData = '';
79 | try {
80 | userConfigData = fs.readFileSync(
81 | baseAppPath + '/' + CONFIG_FILE,
82 | 'utf8'
83 | );
84 | } catch (e) {
85 | logger.error(chalk.red(`Failed to read user config.xml. ${e}`));
86 | util.exit();
87 | return;
88 | }
89 |
90 | const xmlParser = new xml2js.Parser({ attrkey: 'attributes' });
91 |
92 | const parsedXmlData = await new Promise((resolve, reject) =>
93 | xmlParser.parseString(userConfigData, function (err, result) {
94 | resolve(result);
95 | })
96 | );
97 |
98 | if (parsedXmlData && parsedXmlData.widget) {
99 | setDefaultConfigData(parsedXmlData.widget);
100 | } else {
101 | logger.log(`User config.xml is not supported format.`);
102 | util.exit();
103 | }
104 |
105 | const xmlBuilder = new xml2js.Builder({
106 | attrkey: 'attributes',
107 | xmldec: { version: '1.0', encoding: 'UTF-8' }
108 | });
109 |
110 | const witsConfigData = xmlBuilder.buildObject(parsedXmlData);
111 |
112 | try {
113 | fs.writeFileSync(
114 | path.join(util.WITS_BASE_PATH, '../', 'container', CONFIG_FILE),
115 | witsConfigData,
116 | 'utf8'
117 | );
118 | } catch (e) {
119 | logger.error(chalk.red(`Failed to write Wits config.xml. ${e}`));
120 | util.exit();
121 | return;
122 | }
123 | }
124 |
125 | function setDefaultConfigData(configData) {
126 | const WITS_CONFIG_APPLICATION = 'tizen:application';
127 | const WITS_CONFIG_ACCESS_TAG = 'access';
128 | const WITS_CONFIG_CONTENT_TAG = 'content';
129 | const WITS_CONFIG_ICON_TAG = 'icon';
130 | const WITS_CONFIG_PRIVILEGE_TAG = 'tizen:privilege';
131 | const FILESYSTEM_READ_PRIVILEGE =
132 | 'http://tizen.org/privilege/filesystem.read';
133 | const FILESYSTEM_WRITE_PRIVILEGE =
134 | 'http://tizen.org/privilege/filesystem.write';
135 |
136 | configData[WITS_CONFIG_APPLICATION][0].attributes.id += WITS_NAME_TAG;
137 |
138 | configData[WITS_CONFIG_ACCESS_TAG] = [
139 | {
140 | attributes: {
141 | origin: '*',
142 | subdomains: 'true'
143 | }
144 | }
145 | ];
146 |
147 | // configData[WITS_CONFIG_CONTENT_TAG] = [
148 | // {
149 | // attributes: {
150 | // src: 'index.html'
151 | // }
152 | // }
153 | // ];
154 |
155 | configData[WITS_CONFIG_ICON_TAG] = [
156 | {
157 | attributes: {
158 | src: 'icon.png'
159 | }
160 | }
161 | ];
162 |
163 | if (configData.hasOwnProperty(WITS_CONFIG_PRIVILEGE_TAG)) {
164 | configData[WITS_CONFIG_PRIVILEGE_TAG].push(
165 | {
166 | attributes: {
167 | name: FILESYSTEM_READ_PRIVILEGE
168 | }
169 | },
170 | {
171 | attributes: {
172 | name: FILESYSTEM_WRITE_PRIVILEGE
173 | }
174 | }
175 | );
176 | } else {
177 | configData[WITS_CONFIG_PRIVILEGE_TAG] = [
178 | {
179 | attributes: {
180 | name: FILESYSTEM_READ_PRIVILEGE
181 | }
182 | },
183 | {
184 | attributes: {
185 | name: FILESYSTEM_WRITE_PRIVILEGE
186 | }
187 | }
188 | ];
189 | }
190 | }
191 |
192 | function setBaseJSData(userAnswer, deviceInfo) {
193 | try {
194 | const file = path.join(
195 | util.WITS_BASE_PATH,
196 | '../',
197 | 'container',
198 | 'js',
199 | 'base.js'
200 | );
201 | const data = fs.readFileSync(file, 'utf8');
202 | const contentSrc = getContentSrc(userAnswer.baseAppPath);
203 | const hostAppId = module.exports.getHostAppId(userAnswer.baseAppPath);
204 | const hostAppName = hostAppId.split('.')[1];
205 | const hostAppPath = deviceInfo.appInstallPath + hostAppName;
206 |
207 | contentSrc.replace(regExp.FIRST_BACKSLASH, '');
208 | const contentFullSrc = util.isRemoteUrl(contentSrc)
209 | ? contentSrc
210 | : hostAppPath +
211 | '/' +
212 | contentSrc.replace(regExp.FIRST_BACKSLASH, '');
213 |
214 | const hostIp = userAnswer.hostIp;
215 |
216 | const convertData = {
217 | '{{CONTENT_PATH}}': hostAppPath,
218 | '{{CONTENT_SRC}}': contentFullSrc,
219 | '{{HOST_IP}}': 'http://' + hostIp,
220 | '{{HOST_PORT}}': userAnswer.socketPort,
221 | '{{HOST_BASE_CONTENT_PATH}}': userAnswer.baseAppPath
222 | };
223 |
224 | const str = data.replace(regExp.HOST_DATA, key => {
225 | return convertData[key];
226 | });
227 |
228 | fs.writeFileSync(
229 | path.join(util.WITS_BASE_PATH, '../', 'container', 'js', 'main.js'),
230 | str,
231 | 'utf8'
232 | );
233 | } catch (e) {
234 | logger.error(chalk.red(`Failed to set Wits baseJS data to file ${e}`));
235 | util.exit();
236 | }
237 | }
238 |
239 | function setBaseHtmlData(userAnswer) {
240 | try {
241 | const file = path.join(
242 | util.WITS_BASE_PATH,
243 | '../',
244 | 'container',
245 | 'base.html'
246 | );
247 | const data = fs.readFileSync(file, 'utf8');
248 |
249 | const str = data.replace(regExp.HOST_WIDTH, userAnswer.width);
250 |
251 | fs.writeFileSync(
252 | path.join(util.WITS_BASE_PATH, '../', 'container', 'index.html'),
253 | str,
254 | 'utf8'
255 | );
256 | } catch (e) {
257 | logger.error(chalk.red(`Failed to set Wits baseHtml data to file`));
258 | util.exit();
259 | }
260 | }
261 |
262 | function getContentSrc(baseAppPath) {
263 | let contentSrc = 'index.html';
264 |
265 | try {
266 | const file = path.resolve(path.join(baseAppPath, 'config.xml'));
267 | let data = fs.readFileSync(file, 'utf8');
268 | data = util.clearComment(data);
269 | contentSrc = data
270 | .match(regExp.CONTENT_SRC)[0]
271 | .replace(regExp.CONTENT_SRC_ATTRIBUTE, '');
272 | } catch (e) {
273 | logger.warn(
274 | `[warning] Failed to read config.xml. Set Content src to default.`
275 | );
276 | }
277 |
278 | logger.log(`content src is : ${contentSrc}`);
279 |
280 | return contentSrc;
281 | }
282 |
283 | function setBaseFsWrapper(userAnswer, deviceInfo) {
284 | try {
285 | const file = path.join(
286 | util.WITS_BASE_PATH,
287 | 'wrapper',
288 | 'baseFilesystemWrapper.js'
289 | );
290 | const data = fs.readFileSync(file, 'utf8');
291 | const hostAppId = module.exports.getHostAppId(userAnswer.baseAppPath);
292 | const hostAppName = hostAppId.split('.')[1];
293 | const hostAppPath = deviceInfo.appInstallPath + hostAppName;
294 |
295 | const convertData = {
296 | '{{CONTENT_PATH}}': hostAppPath
297 | };
298 |
299 | const str = data.replace(regExp.HOST_DATA, key => {
300 | return convertData[key];
301 | });
302 |
303 | fs.writeFileSync(
304 | path.join(util.WITS_BASE_PATH, 'wrapper', 'filesystemWrapper.js'),
305 | str,
306 | 'utf8'
307 | );
308 | } catch (e) {
309 | console.error(
310 | chalk.red(`Failed to set Wits baseFsWrapper data to file ${e}`)
311 | );
312 | }
313 | }
314 |
--------------------------------------------------------------------------------
/lib/userInfoHelper.js:
--------------------------------------------------------------------------------
1 | const inquirer = require('inquirer');
2 | const chalk = require('chalk');
3 | const fs = require('fs');
4 | const os = require('os');
5 | const path = require('path');
6 | const _ = require('lodash');
7 |
8 | const util = require('./util.js');
9 | const deviceConnectHelper = require('./deviceConnectHelper.js');
10 | const regExp = require('./regexp.js');
11 | const { logger } = require('./logger');
12 |
13 | const EMULATOR_IP = '0.0.0.0';
14 | const WITS_CONFIG_FILE_NAME = '.witsconfig.json';
15 |
16 | module.exports = {
17 | WITS_USER_DATA: null,
18 | getRefinedData: () => {
19 | const result = {};
20 |
21 | const wInfo = module.exports.getLatestWitsconfigInfo();
22 | const cInfo = wInfo.connectionInfo;
23 | const pInfo = wInfo.profileInfo;
24 |
25 | const baseAppPath = module.exports.getBaseAppPath(cInfo.baseAppPath);
26 |
27 | util.setCurrentAppPath(baseAppPath);
28 | result.baseAppPath = baseAppPath;
29 |
30 | result.width = cInfo.width;
31 | result.deviceIp = cInfo.deviceIp;
32 | result.socketPort = cInfo.socketPort;
33 | result.hostIp = cInfo.hostIp;
34 | result.isDebugMode = cInfo.isDebugMode;
35 | result.profileName = pInfo.name;
36 | result.profilePath = pInfo.path;
37 |
38 | displayStoredInfo(result);
39 |
40 | return result;
41 | },
42 | askQuestion: async cInfo => {
43 | const result = {};
44 | const ask = await getUserAskData();
45 | const answer = await inquirer.prompt(ask);
46 | const baseAppPath = module.exports.getBaseAppPath(answer.baseAppPath);
47 | const hostIp = util.getValidHostIp(cInfo, answer);
48 |
49 | answer.socketPort = util.getSocketPort();
50 | answer.hostIp = hostIp;
51 |
52 | result.baseAppPath = baseAppPath;
53 | result.width = answer.width;
54 | result.deviceIp = answer.deviceIp;
55 | result.socketPort = answer.socketPort;
56 | result.hostIp = hostIp;
57 | result.isDebugMode = answer.isDebugMode;
58 | result.profileName = answer.profileName;
59 | result.profilePath = answer.profilePath;
60 |
61 | await module.exports.updateLatestUserAnswer(answer);
62 | return result;
63 | },
64 | getDeviceInfo: async deviceIp => {
65 | if ((deviceIp === null) | (deviceIp === undefined)) {
66 | logger.error(
67 | chalk.red(
68 | 'There is no deviceIp, Please do "wits -i" for configuration.'
69 | )
70 | );
71 | util.exit();
72 | }
73 | return await deviceConnectHelper.getConnectedDeviceInfo(deviceIp);
74 | },
75 | getLatestWitsconfigInfo: () => {
76 | const result = initWitsconfigInfo();
77 | try {
78 | const wInfo = getWitsconfigData();
79 |
80 | const cInfo = wInfo.connectionInfo;
81 | const pInfo = wInfo.profileInfo;
82 |
83 | for (const key in cInfo) {
84 | result.connectionInfo[key] = cInfo[key];
85 | }
86 |
87 | for (const key in pInfo) {
88 | result.profileInfo[key] = pInfo[key];
89 | }
90 |
91 | if (util.isPropertyExist(wInfo, 'optionalInfo')) {
92 | result.optionalInfo = wInfo.optionalInfo;
93 | }
94 |
95 | return result;
96 | } catch (e) {
97 | logger.warn(`[warning] Failed to getLatestWitsconfigInfo >> ${e}`);
98 | }
99 | },
100 | getBaseAppPath: baseAppPath => {
101 | const appPath = baseAppPath ? baseAppPath : '.';
102 | return path.isAbsolute(appPath)
103 | ? appPath.replace(regExp.BACKSLASH, '/')
104 | : util.getAbsolutePath(appPath);
105 | },
106 | getOptionalInfo: async () => {
107 | const CONFIG_PATH = path.join(
108 | util.CURRENT_PROJECT_PATH,
109 | WITS_CONFIG_FILE_NAME
110 | );
111 |
112 | if (!util.isFileExist(CONFIG_PATH)) {
113 | return null;
114 | }
115 | const data = fs.readFileSync(CONFIG_PATH, 'utf8');
116 | if (data !== '' && typeof data === 'string') {
117 | const witsConfigData = JSON.parse(data);
118 | if (util.isPropertyExist(witsConfigData, 'optionalInfo')) {
119 | return witsConfigData.optionalInfo;
120 | }
121 | }
122 | return null;
123 | },
124 | updateLatestUserAnswer: async userAnswer => {
125 | const savingInfo = {};
126 | const wInfo = module.exports.getLatestWitsconfigInfo();
127 | const cInfo = wInfo.connectionInfo;
128 | const pInfo = wInfo.profileInfo;
129 |
130 | const latestConnectionInfo = {
131 | deviceIp: userAnswer.deviceIp ? userAnswer.deviceIp : cInfo.deviceIp,
132 | hostIp: userAnswer.hostIp ? userAnswer.hostIp : cInfo.hostIp,
133 | socketPort: userAnswer.socketPort
134 | ? userAnswer.socketPort
135 | : cInfo.socketPort,
136 | width: userAnswer.width ? userAnswer.width : cInfo.width,
137 | isDebugMode: userAnswer.hasOwnProperty('isDebugMode')
138 | ? userAnswer.isDebugMode
139 | : cInfo.isDebugMode
140 | };
141 |
142 | const profilePath = userAnswer.profilePath
143 | ? userAnswer.profilePath
144 | : pInfo.path;
145 | const latestProfileInfo = {
146 | path: profilePath.trim()
147 | };
148 |
149 | if (userAnswer.baseAppPath) {
150 | latestConnectionInfo['baseAppPath'] = userAnswer.baseAppPath;
151 | util.CURRENT_PROJECT_PATH = userAnswer.baseAppPath;
152 | }
153 |
154 | if (cInfo.baseAppPaths) {
155 | latestConnectionInfo['baseAppPaths'] = cInfo.baseAppPaths;
156 | }
157 |
158 | if (userAnswer.hostIp) {
159 | latestConnectionInfo.hostIp = userAnswer.hostIp;
160 | }
161 |
162 | savingInfo.connectionInfo = latestConnectionInfo;
163 | savingInfo.profileInfo = latestProfileInfo;
164 |
165 | if (util.isPropertyExist(wInfo, 'optionalInfo')) {
166 | savingInfo.optionalInfo = wInfo.optionalInfo;
167 | }
168 |
169 | module.exports.WITS_USER_DATA = savingInfo;
170 |
171 | try {
172 | fs.writeFileSync(
173 | path.join(util.CURRENT_PROJECT_PATH, WITS_CONFIG_FILE_NAME),
174 | JSON.stringify(savingInfo, null, 2),
175 | 'utf8'
176 | );
177 | } catch (e) {
178 | logger.warn('[warning] Failed to set recently connection info');
179 | }
180 | }
181 | };
182 |
183 | function initWitsconfigInfo() {
184 | return {
185 | connectionInfo: {
186 | deviceIp: null,
187 | socketPort: '8498',
188 | width: '1920',
189 | isDebugMode: false
190 | },
191 | profileInfo: {
192 | path: getProfilePath()
193 | }
194 | };
195 | }
196 |
197 | function getProfilePath() {
198 | let profilePath = '';
199 | switch (util.PLATFORM) {
200 | case 'win32':
201 | profilePath = 'C:/tizen-studio-data/profile/profiles.xml';
202 | break;
203 | case 'linux':
204 | default:
205 | profilePath = path.resolve(
206 | os.homedir(),
207 | 'tizen-studio-data',
208 | 'profile',
209 | 'profiles.xml'
210 | );
211 | break;
212 | }
213 |
214 | if (!util.isFileExist(profilePath)) {
215 | profilePath = path.resolve(
216 | path.join(util.WITS_BASE_PATH, '..', 'resource', 'profiles.xml')
217 | );
218 | }
219 | return profilePath;
220 | }
221 |
222 | function displayStoredInfo(data) {
223 | logger.log(``);
224 | logger.log(` > [ Stored Information ]`);
225 | logger.log(``);
226 | logger.log(` > baseAppPath : ${data.baseAppPath}`);
227 | logger.log(` > width : ${data.width}`);
228 | logger.log(` > deviceIp : ${data.deviceIp}`);
229 | logger.log(` > isDebugMode : ${data.isDebugMode}`);
230 | logger.log(``);
231 | logger.log(` > profile path : ${data.profilePath}`);
232 | logger.log(` > hostIp : ${data.hostIp}`);
233 | logger.log(``);
234 | }
235 |
236 | function getWitsconfigData() {
237 | try {
238 | const witsConfigInfo = JSON.parse(
239 | fs.readFileSync(
240 | path.join(util.CURRENT_PROJECT_PATH, WITS_CONFIG_FILE_NAME),
241 | 'utf8'
242 | )
243 | );
244 | return witsConfigInfo;
245 | } catch (e) {
246 | logger.error(chalk.red(`Failed to getWitsconfig: ${e}`));
247 | util.exit();
248 | }
249 | }
250 |
251 | async function getUserAskData() {
252 | const wData = module.exports.getLatestWitsconfigInfo();
253 | const cInfo = wData.connectionInfo;
254 | const pInfo = wData.profileInfo;
255 |
256 | const baseAppPathQuestion = getBaseAppPathQuestion(cInfo);
257 | const hostIpQuestion = getHostIpQuestion(cInfo);
258 |
259 | const ask = [];
260 | ask.push(getDeviceIpQuestion(cInfo));
261 | if (hostIpQuestion !== null) {
262 | ask.push(hostIpQuestion);
263 | }
264 | ask.push(getWidthQuestion(cInfo));
265 | ask.push(getProfilePathQuestion(pInfo));
266 | ask.push(getIsDebugModeQuestion(cInfo));
267 | baseAppPathQuestion.type && ask.unshift(baseAppPathQuestion);
268 |
269 | return ask;
270 | }
271 |
272 | function getDeviceIpQuestion(cInfo) {
273 | return {
274 | type: 'input',
275 | name: 'deviceIp',
276 | message:
277 | 'Input your Device Ip address(If using Emulator, input ' +
278 | EMULATOR_IP +
279 | ') :',
280 | default: cInfo.deviceIp,
281 | validate: function (input) {
282 | return util.isIpAddress(input)
283 | ? true
284 | : 'Invalid format of Ip address which is entered.';
285 | }
286 | };
287 | }
288 |
289 | function getWidthQuestion(cInfo) {
290 | return {
291 | type: 'input',
292 | name: 'width',
293 | message: 'Input your Application width (1920 or 1280) :',
294 | default: cInfo.width,
295 | validate: function (input) {
296 | return input === '1920' || input === '1280'
297 | ? true
298 | : 'Tizen web Application only support 1920 or 1280 width';
299 | }
300 | };
301 | }
302 |
303 | function getProfilePathQuestion(pInfo) {
304 | return {
305 | type: 'input',
306 | name: 'profilePath',
307 | message: 'Input the path of profile.xml :',
308 | default: pInfo.path
309 | };
310 | }
311 |
312 | function getIsDebugModeQuestion(cInfo) {
313 | return {
314 | type: 'confirm',
315 | name: 'isDebugMode',
316 | message: 'Do you want to launch with chrome DevTools? : ',
317 | default: cInfo.isDebugMode
318 | };
319 | }
320 |
321 | function getHostIpQuestion(cInfo) {
322 | let question = {};
323 | let recentlyIndex = 0;
324 | const addresses = util.getHostIpAddresses();
325 | if (addresses.length <= 1) {
326 | return null;
327 | }
328 | if (cInfo.hostIp) {
329 | recentlyIndex = addresses.indexOf(cInfo.hostIp);
330 | }
331 | question = {
332 | type: 'list',
333 | name: 'hostIp',
334 | message: 'Select your valid PC Ip address for connecting TV:',
335 | choices: addresses,
336 | default: recentlyIndex
337 | };
338 | return question;
339 | }
340 |
341 | function getBaseAppPathQuestion(connectionInfo) {
342 | let question = {};
343 | let baseAppPathIndex = 0;
344 | if (connectionInfo.baseAppPaths) {
345 | if (connectionInfo.baseAppPaths.length === 1) {
346 | question = {
347 | type: 'input',
348 | name: 'baseAppPath',
349 | message: 'Input your Application Path :',
350 | default: connectionInfo.baseAppPaths[0]
351 | };
352 | } else {
353 | baseAppPathIndex = connectionInfo.baseAppPaths.indexOf(
354 | connectionInfo.baseAppPath
355 | );
356 | question = {
357 | type: 'list',
358 | name: 'baseAppPath',
359 | message: 'Select the app path to launch Wits :',
360 | choices: connectionInfo.baseAppPaths,
361 | default: baseAppPathIndex >= 0 ? baseAppPathIndex : 0
362 | };
363 | }
364 | }
365 | return question;
366 | }
367 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | APPENDIX: How to apply the Apache License to your work.
179 |
180 | To apply the Apache License to your work, attach the following
181 | boilerplate notice, with the fields enclosed by brackets "[]"
182 | replaced with your own identifying information. (Don't include
183 | the brackets!) The text should be enclosed in the appropriate
184 | comment syntax for the file format. We also recommend that a
185 | file or class name and description of purpose be included on the
186 | same "printed page" as the copyright notice for easier
187 | identification within third-party archives.
188 |
189 | Copyright [yyyy] [name of copyright owner]
190 |
191 | Licensed under the Apache License, Version 2.0 (the "License");
192 | you may not use this file except in compliance with the License.
193 | You may obtain a copy of the License at
194 |
195 | http://www.apache.org/licenses/LICENSE-2.0
196 |
197 | Unless required by applicable law or agreed to in writing, software
198 | distributed under the License is distributed on an "AS IS" BASIS,
199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 | See the License for the specific language governing permissions and
201 | limitations under the License.
202 |
--------------------------------------------------------------------------------
/container/LICENSE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | APPENDIX: How to apply the Apache License to your work.
179 |
180 | To apply the Apache License to your work, attach the following
181 | boilerplate notice, with the fields enclosed by brackets "[]"
182 | replaced with your own identifying information. (Don't include
183 | the brackets!) The text should be enclosed in the appropriate
184 | comment syntax for the file format. We also recommend that a
185 | file or class name and description of purpose be included on the
186 | same "printed page" as the copyright notice for easier
187 | identification within third-party archives.
188 |
189 | Copyright [yyyy] [name of copyright owner]
190 |
191 | Licensed under the Apache License, Version 2.0 (the "License");
192 | you may not use this file except in compliance with the License.
193 | You may obtain a copy of the License at
194 |
195 | http://www.apache.org/licenses/LICENSE-2.0
196 |
197 | Unless required by applicable law or agreed to in writing, software
198 | distributed under the License is distributed on an "AS IS" BASIS,
199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 | See the License for the specific language governing permissions and
201 | limitations under the License.
202 |
203 |
--------------------------------------------------------------------------------