[Service][AppManifest] Fix issue of making unique app id
[platform/framework/web/wrtjs.git] / wrt_app / service / builtins / appmanifest_loader.ts
1 import '../../common/init';
2 import { isMainThread, workerData } from 'worker_threads';
3 import { wrt } from '../../browser/wrt';
4 import { URL } from 'url';
5 import * as fs from 'fs';
6 import * as https from 'https';
7 import * as XWalkExtension from '../../common/wrt_xwalk_extension';
8
9 function getManifestFile(manifestUrl: string) {
10   console.log('manifestUrl : '+manifestUrl);
11   return new Promise((resolve, reject) => {
12     const req = https.request(manifestUrl, (res) => {
13       res.setEncoding('utf8');
14       let responseBody = '';
15       res.on('data', (data) => {
16         responseBody += data;
17       });
18       res.on('end', () => {
19         resolve(JSON.parse(responseBody));
20       });
21     }).on('error', (err) => {
22       console.log(`error : ${err}`);
23       reject(err);
24     });
25     req.end();
26   });
27 }
28
29 async function downloadIcon(iconSrc: string, iconFile: string) {
30   console.log('iconSrc : ' + iconSrc);
31   return new Promise((resolve, reject) => {
32     const req = https.request(iconSrc, (res) => {
33       const Stream = require('stream').Transform;
34       let data = new Stream();
35       res.on('data', (chunk) => {
36         data.push(chunk);
37       });
38       res.on('end', () => {
39         fs.writeFileSync(iconFile, data.read());
40         resolve('done');
41       });
42     }).on('error', (err) => {
43       console.log(`error : ${err}`);
44       reject(err);
45     });
46     req.end();
47   });
48 }
49
50 function makeFileSync(file: string, data: string) {
51   fs.writeFileSync(file, data);
52 }
53
54 function concatWithBaseUrl(path: string, baseUrl: string) {
55   if (path.substr(0, 1) === '/') {
56     const url = new URL(path, baseUrl);
57     return url.toString();
58   } else if (path.indexOf('http:') == -1 && path.indexOf('https:') == -1) {
59     return baseUrl.substr(0, baseUrl.lastIndexOf('/') + 1) + path;
60   }
61   return path;
62 }
63
64 let baseWorkingDir = '/home/owner/content/Downloads/AppManifest';
65 let downloadVirtualDir = 'downloads/AppManifest';
66 let iconFile: string = '';
67 let iconName: string = '';
68 let manifestFile: string = '';
69 let convertedConfigXml: string = '';
70 let refCount: number = 0;
71
72 function getAppName(appName: string) {
73   appName = appName.replace(/ /g, '');
74   console.log('appName : ' + appName);
75   return appName;
76 }
77
78 function makeWorkingFolder(appName: string) {
79   let workingDir = `${baseWorkingDir}/${appName}`;
80   fs.rmdirSync(workingDir, { recursive: true });
81   fs.mkdir(workingDir, { recursive: true }, (err) => {
82     if (err)
83       console.log(`mkdir error : ${err}`)
84   });
85 }
86
87 async function handleIcon(appName: string, manifestUrl: string, manifest: any) {
88   let lengthOfIcons = manifest['icons'].length;
89   let lastIcon = manifest['icons'][lengthOfIcons - 1];
90   let iconSrc = concatWithBaseUrl(lastIcon['src'], manifestUrl);
91   iconName = iconSrc.substr(iconSrc.lastIndexOf('/') + 1);
92   iconFile = `${baseWorkingDir}/${appName}/${iconName}`;
93   await downloadIcon(iconSrc, iconFile);
94   iconFile = `${downloadVirtualDir}/${appName}/${iconName}`;
95   refCount++;
96 }
97
98 function makeManifestFile(appName: string, manifest: any) {
99   manifestFile = `${baseWorkingDir}/${appName}/appmanifest.json`;
100   makeFileSync(manifestFile, JSON.stringify(manifest));
101   manifestFile = `${downloadVirtualDir}/${appName}/appmanifest.json`;
102   refCount++;
103 }
104
105 function makePkgId(startUrl: string) {
106   let id = Buffer.from(startUrl).toString('base64');
107   id = id.replace(/=/gi, '');
108   console.log(`id : ${id}`);
109   return id.substr(-10);
110 }
111
112 function convertConfigXml(appName: string, startUrl: string) {
113   convertedConfigXml = `${baseWorkingDir}/${appName}/config.xml`;
114   let id = makePkgId(startUrl);
115   let configXml = `<?xml version='1.0' encoding='UTF-8'?>`;
116   configXml += `<widget xmlns='http://www.w3.org/ns/widgets' xmlns:tizen='http://tizen.org/ns/widgets' id='http://yourdomain/AppManifest' version='1.0.0' viewmodes='maximized'>`;
117   configXml += `<tizen:application id='${id}.${appName}' package='${id}' required_version='3.0' />`;
118   configXml += `<content src='${startUrl}' />`
119   configXml += `<icon src='${iconName}' />`;
120   configXml += `<name>${appName}</name>`;
121   configXml += `<access origin='*' subdomains='true' />`;
122   configXml += `</widget>`;
123   makeFileSync(convertedConfigXml, configXml);
124   convertedConfigXml = `${downloadVirtualDir}/${appName}/config.xml`;
125   refCount++;
126 }
127
128 function cleanUpAndQuit(appName: string) {
129   let workingDir = `${baseWorkingDir}/${appName}`;
130   fs.rmdirSync(workingDir, { recursive: true });
131   process.exit();
132 }
133
134 function installWgt(appName: string) {
135   let wgtPath = `${baseWorkingDir}/${appName}/${appName}.wgt`;
136   let installinfo = "{\"app_id\":\"" + appName + "\",\"pkg_path\":\"" + wgtPath + "\"}";
137   console.log(`installWgt info: ${installinfo}`);
138   (wrt.tv as any).notifyInstall(installinfo);
139   process.exit();
140 }
141
142 function makeWgt(appName: string) {
143   let wgtPath = `${downloadVirtualDir}/${appName}/${appName}.wgt`;
144   let onArchive = (archive: any) => {
145     function progressCallback(opId: string, val: number, name: any) {
146       console.log('opId: ' + opId + ' with progress val: ' + (val * 100).toFixed(0) + '%');
147     }
148     function successCallback() {
149       console.log(`File added : ${refCount}`);
150       refCount--;
151       if (!refCount) {
152         installWgt(appName);
153       }
154     }
155     console.log(`convertedConfigXml : ${convertedConfigXml}`);
156     console.log(`manifestFile : ${manifestFile}`);
157     console.log(`iconFile : ${iconFile}`);
158     let defaultArchiveFileEntryOption = { destination:'', stripSourceDirectory: true};
159     archive.add(convertedConfigXml, successCallback, null, progressCallback, defaultArchiveFileEntryOption);
160     archive.add(manifestFile, successCallback, null, progressCallback, defaultArchiveFileEntryOption);
161     if (iconFile)
162       archive.add(iconFile, successCallback, null, progressCallback, defaultArchiveFileEntryOption);
163   }
164   global.tizen.archive.open(wgtPath, 'w', onArchive, () => { }, { overwrite: true });
165 }
166
167 async function parseAndHandleManifest(manifestUrl: string) {
168   let manifest: any = await getManifestFile(manifestUrl);
169   let appName = getAppName(manifest['name']);
170   try {
171     makeWorkingFolder(appName);
172     if (manifest['icons']) {
173       await handleIcon(appName, manifestUrl, manifest);
174     }
175     if (manifest['start_url']) {
176       let startUrl = concatWithBaseUrl(manifest['start_url'], manifestUrl);
177       manifest['start_url'] = startUrl;
178
179       makeManifestFile(appName, manifest);
180       convertConfigXml(appName, startUrl);
181     }
182     makeWgt(appName);
183   } catch (e) {
184     console.log(`Exception: ${e}`);
185     cleanUpAndQuit(appName);
186   }
187 }
188
189 export function run(manifestUrl: string) {
190   console.log(`Appmanifest parser starts for ${manifestUrl}`);
191   setInterval(() => { }, 500);
192   wrt.tv?.delayShutdown();
193   XWalkExtension.initialize();
194   parseAndHandleManifest(manifestUrl);
195 }
196
197 if (!isMainThread) {
198   run(decodeURIComponent(workerData.id));
199 }