From: DongHyun Song Date: Thu, 12 Nov 2020 08:17:18 +0000 (+0900) Subject: [Service][Builtin] Introduce Appmanifest installer X-Git-Tag: submit/tizen/20210127.160020^0 X-Git-Url: http://review.tizen.org/git/?a=commitdiff_plain;h=51a7882ab7ea3f5fedc35e0f1ec4267f854509a5;p=platform%2Fframework%2Fweb%2Fwrtjs.git [Service][Builtin] Introduce Appmanifest installer 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 --- diff --git a/packaging/plugins.json b/packaging/plugins.json index 7838bf7b..ed9a833f 100644 --- a/packaging/plugins.json +++ b/packaging/plugins.json @@ -79,5 +79,10 @@ "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": [] } ] diff --git a/wrt_app/common/service_manager.ts b/wrt_app/common/service_manager.ts index face443b..544aec76 100644 --- a/wrt_app/common/service_manager.ts +++ b/wrt_app/common/service_manager.ts @@ -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 index 00000000..2c8597bd --- /dev/null +++ b/wrt_app/service/builtins/appmanifest_loader.ts @@ -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 = ``; + configXml += ``; + configXml += ``; + configXml += `` + configXml += ``; + configXml += `${appName}`; + configXml += ``; + configXml += ``; + 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)); +} diff --git a/wrt_app/service/builtins/wasm_builder.ts b/wrt_app/service/builtins/wasm_builder.ts index 54177d0b..e23ac17b 100644 --- a/wrt_app/service/builtins/wasm_builder.ts +++ b/wrt_app/service/builtins/wasm_builder.ts @@ -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);