[Service][Builtin] Introduce Appmanifest installer 86/247586/10 submit/tizen/20210127.160020 submit/tizen/20210201.022015
authorDongHyun Song <dh81.song@samsung.com>
Thu, 12 Nov 2020 08:17:18 +0000 (17:17 +0900)
committerDongHyun Song <dh81.song@samsung.com>
Fri, 22 Jan 2021 06:51:02 +0000 (15:51 +0900)
This built-in service introduces to support W3C AppManifest
installation, which is written JSON manifest file.

The overall goal of this feature is that
 1) Convert JSON manifest to config.xml
    - download proper icon
 2) Archive .wgt and request to install .wgt
    - tizen.archive is newly necessary to load

To support installation of archived wgt file from this feature,
there are 2 consideration to be solved.
 a) there is no signature file
 b) at least, public level certification is necessary

Change-Id: Iaf09bdfb3e6c5d6e8471ef91ea13fda863641db0
Signed-off-by: DongHyun Song <dh81.song@samsung.com>
packaging/plugins.json
wrt_app/common/service_manager.ts
wrt_app/service/builtins/appmanifest_loader.ts [new file with mode: 0644]
wrt_app/service/builtins/wasm_builder.ts

index 7838bf7ba80d45456136b002c2332bb8c0a4d4e9..ed9a833f07b0a97840111f0bc2a56c7d00ee68a6 100644 (file)
     "name":"tizen.notification",
     "lib":"/usr/lib/tizen-extensions-crosswalk/libtizen_notification.so",
     "entry_points": ["tizen.StatusNotification","tizen.UserNotification", "tizen.NotificationDetailInfo"]
+  },
+  {
+    "name":"tizen.archive",
+    "lib":"/usr/lib/tizen-extensions-crosswalk/libtizen_archive.so",
+    "entry_points": []
   }
 ]
index face443bcce7af80500b218ee884aaf160b3480d..544aec7663d9e6bd89d8afa3e910fc7b8dc44e99 100644 (file)
@@ -60,6 +60,7 @@ export function handleBuiltinService(serviceId: string, serviceName: string) {
   }
   let need_stop = (serviceName.substr(0, 5) === 'stop_');
   if (need_stop) {
+    console.log(`${serviceName} will be terminated.`);
     workers[serviceId].terminate();
   } else {
     console.log(`Builtin service is ${serviceName}`);
diff --git a/wrt_app/service/builtins/appmanifest_loader.ts b/wrt_app/service/builtins/appmanifest_loader.ts
new file mode 100644 (file)
index 0000000..2c8597b
--- /dev/null
@@ -0,0 +1,203 @@
+import '../../common/init';
+import { isMainThread, workerData } from 'worker_threads';
+import { wrt } from '../../browser/wrt';
+import * as fs from 'fs';
+import * as https from 'https';
+import * as XWalkExtension from '../../common/wrt_xwalk_extension';
+
+function getManifestFile(manifestUrl: string) {
+  console.log('manifestUrl : '+manifestUrl);
+  return new Promise((resolve, reject) => {
+    const req = https.request(manifestUrl, (res) => {
+      res.setEncoding('utf8');
+      let responseBody = '';
+      res.on('data', (data) => {
+        responseBody += data;
+      });
+      res.on('end', () => {
+        resolve(JSON.parse(responseBody));
+      });
+    }).on('error', (err) => {
+      console.log(`error : ${err}`);
+      reject(err);
+    });
+    req.end();
+  });
+}
+
+async function downloadIcon(iconSrc: string, iconFile: string) {
+  console.log('iconSrc : ' + iconSrc);
+  return new Promise((resolve, reject) => {
+    const req = https.request(iconSrc, (res) => {
+      const Stream = require('stream').Transform;
+      let data = new Stream();
+      res.on('data', (chunk) => {
+        data.push(chunk);
+      });
+      res.on('end', () => {
+        fs.writeFileSync(iconFile, data.read());
+        resolve('done');
+      });
+    }).on('error', (err) => {
+      console.log(`error : ${err}`);
+      reject(err);
+    });
+    req.end();
+  });
+}
+
+function copyManifest(filePath: string, manifestData: string) {
+  fs.writeFileSync(filePath, manifestData);
+}
+
+function concatWithBaseUrl(url: string, baseUrl: string) {
+  if (url.indexOf('http:') == -1 && url.indexOf('https:') == -1) {
+    return baseUrl.substr(0, baseUrl.lastIndexOf('/') + 1) + url;
+  }
+  return url;
+}
+
+let baseWorkingDir = '/home/owner/content/Downloads/AppManifest';
+let downloadVirtualDir = 'downloads/AppManifest';
+let iconFile: string = '';
+let iconName: string = '';
+let manifestFile: string = '';
+let convertedConfigXml: string = '';
+let refCount: number = 0;
+
+function getAppName(appName: string) {
+  appName = appName.replace(/ /g, '');
+  console.log('appName : ' + appName);
+  return appName;
+}
+
+function makeWorkingFolder(appName: string) {
+  let workingDir = `${baseWorkingDir}/${appName}`;
+  fs.rmdirSync(workingDir, { recursive: true });
+  fs.mkdir(workingDir, { recursive: true }, (err) => {
+    if (err)
+      console.log(`mkdir error : ${err}`)
+  });
+}
+
+async function handleIcon(appName: string, manifestUrl: string, manifest: any) {
+  let lengthOfIcons = manifest['icons'].length;
+  let lastIcon = manifest['icons'][lengthOfIcons - 1];
+  let iconSrc = concatWithBaseUrl(lastIcon['src'], manifestUrl);
+  iconName = iconSrc.substr(iconSrc.lastIndexOf('/') + 1);
+  iconFile = `${baseWorkingDir}/${appName}/${iconName}`;
+  await downloadIcon(iconSrc, iconFile);
+  iconFile = `${downloadVirtualDir}/${appName}/${iconName}`;
+  refCount++;
+}
+
+function handleStartUrl(appName: string, manifestUrl: string, manifest: any) {
+  let startUrl = concatWithBaseUrl(manifest['start_url'], manifestUrl);
+  manifest['start_url'] = startUrl;
+  manifestFile = `${baseWorkingDir}/${appName}/appmanifest.json`;
+  copyManifest(manifestFile, JSON.stringify(manifest));
+  manifestFile = `${downloadVirtualDir}/${appName}/appmanifest.json`;
+  refCount++;
+  return startUrl;
+}
+
+function makeRandomId() {
+  return Math.random().toString(36).substring(2, 12);
+}
+
+function convertConfigXml(appName: string, startUrl: string) {
+  convertedConfigXml = `${baseWorkingDir}/${appName}/config.xml`;
+  let id = makeRandomId();
+  let configXml = `<?xml version='1.0' encoding='UTF-8'?>`;
+  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'>`;
+  configXml += `<tizen:application id='${id}.${appName}' package='${id}' required_version='2.3' />`;
+  configXml += `<content src='${startUrl}' />`
+  configXml += `<icon src='${iconName}' />`;
+  configXml += `<name>${appName}</name>`;
+  configXml += `<access origin='*' subdomains='true' />`;
+  configXml += `</widget>`;
+  copyManifest(convertedConfigXml, configXml);
+  convertedConfigXml = `${downloadVirtualDir}/${appName}/config.xml`;
+  refCount++;
+}
+
+function cleanUpAndQuit(appName: string) {
+  let workingDir = `${baseWorkingDir}/${appName}`;
+  fs.rmdirSync(workingDir, { recursive: true });
+  process.exit();
+}
+
+function installWgt(appName: string) {
+  let onInstallation = {
+    onprogress: (packageId: string, percentage: string) => {
+      console.log("On installation(" + packageId + ") : progress(" + percentage + ")");
+    },
+    oncomplete: (packageId: string) => {
+      console.log("Installation(" + packageId + ") Complete");
+      wrt.postPlainNotification(appName, 'Install Success', 5);
+      cleanUpAndQuit(appName);
+    }
+  };
+  let wgtPath = `${baseWorkingDir}/${appName}/${appName}.wgt`;
+  console.log(`wgtPath: ${wgtPath}`);
+  global.tizen.package.install(wgtPath, onInstallation, (err: any) => {
+    console.log("Error occurred on installation : " + err.name);
+    cleanUpAndQuit(appName);
+  });
+}
+
+function makeWgt(appName: string) {
+  let wgtPath = `${downloadVirtualDir}/${appName}/${appName}.wgt`;
+  let onArchive = (archive: any) => {
+    function progressCallback(opId: string, val: number, name: any) {
+      console.log('opId: ' + opId + ' with progress val: ' + (val * 100).toFixed(0) + '%');
+    }
+    function successCallback() {
+      console.log(`File added : ${refCount}`);
+      refCount--;
+      if (!refCount) {
+        installWgt(appName);
+      }
+    }
+    console.log(`convertedConfigXml : ${convertedConfigXml}`);
+    console.log(`manifestFile : ${manifestFile}`);
+    console.log(`iconFile : ${iconFile}`);
+    let defaultArchiveFileEntryOption = { destination:'', stripSourceDirectory: true};
+    archive.add(convertedConfigXml, successCallback, null, progressCallback, defaultArchiveFileEntryOption);
+    archive.add(manifestFile, successCallback, null, progressCallback, defaultArchiveFileEntryOption);
+    if (iconFile)
+      archive.add(iconFile, successCallback, null, progressCallback, defaultArchiveFileEntryOption);
+  }
+  global.tizen.archive.open(wgtPath, 'w', onArchive, () => { }, { overwrite: true });
+}
+
+async function parseAndHandleManifest(manifestUrl: string) {
+  let manifest: any = await getManifestFile(manifestUrl);
+  let appName = getAppName(manifest['name']);
+  try {
+    makeWorkingFolder(appName);
+    if (manifest['icons']) {
+      await handleIcon(appName, manifestUrl, manifest);
+    }
+    if (manifest['start_url']) {
+      let startUrl = handleStartUrl(appName, manifestUrl, manifest);
+      convertConfigXml(appName, startUrl);
+    }
+    makeWgt(appName);
+  } catch (e) {
+    console.log(`Exception: ${e}`);
+    cleanUpAndQuit(appName);
+  }
+}
+
+export function run(manifestUrl: string) {
+  console.log(`Appmanifest parser starts for ${manifestUrl}`);
+  setInterval(() => { }, 500);
+  wrt.tv?.delayShutdown();
+  XWalkExtension.initialize();
+  parseAndHandleManifest(manifestUrl);
+}
+
+if (!isMainThread) {
+  run(decodeURIComponent(workerData.id));
+}
index 54177d0b1a9bda2cc82bea778f7ac9714d764c97..e23ac17b7252da145e49730bec72276222edfaed 100644 (file)
@@ -1,13 +1,13 @@
 import '../../common/init';
-import { isMainThread, parentPort, workerData } from 'worker_threads';
+import { isMainThread, workerData } from 'worker_threads';
 import { wrt } from '../../browser/wrt';
 import * as fs from 'fs';
 
 function compileWasmForCaching(files: string[]) {
   try {
-    files.forEach(async file_path => {
-      console.log(`Requesting WASM compilation for building a cache, file_path:(${file_path})`);
-      let source = fs.readFileSync(file_path);
+    files.forEach(async filePath => {
+      console.log(`Requesting WASM compilation for building a cache, file_path:(${filePath})`);
+      let source = fs.readFileSync(filePath);
       let file = new Uint8Array(source);
       await WebAssembly.compile(file);
     });
@@ -16,12 +16,12 @@ function compileWasmForCaching(files: string[]) {
   }
 }
 
-export function run(app_id: string) {
-  console.log(`wasm_builder.js starts, app_id:(${app_id})`);
+export function run(appId: string) {
+  console.log(`wasm_builder.js starts, app_id:(${appId})`);
   let tv = wrt.tv as NativeWRTjs.TVExtension;
   tv.setWasmFlags();
-  tv.setDiskCache(app_id);
-  let files = tv.getWasmFiles(app_id);
+  tv.setDiskCache(appId);
+  let files = tv.getWasmFiles(appId);
   console.log(files);
   tv.delayShutdown();
   compileWasmForCaching(files);