From 896a407d511e7961c7160c771a01fdbb9fc3772a Mon Sep 17 00:00:00 2001 From: DongHyun Song Date: Mon, 14 Mar 2022 15:11:20 +0900 Subject: [PATCH 01/16] [Service][TV] Introduce ServiceMessage This introduces service message notification for DeviceHome The case doesn't use Samsung IME, the UI app can utilize ServiceMessage to catch propagating message from DeviceHome. notifyServiceMessage() will give a message only to foreground application. Change-Id: If36cb754382fc2c0f6d186c212a40c28ccf3eb9b Signed-off-by: DongHyun Song --- wrt_app/common/service_message.ts | 20 ++++++++++++++++++++ wrt_app/service/device_api_router.ts | 2 ++ 2 files changed, 22 insertions(+) create mode 100644 wrt_app/common/service_message.ts diff --git a/wrt_app/common/service_message.ts b/wrt_app/common/service_message.ts new file mode 100644 index 0000000..4080701 --- /dev/null +++ b/wrt_app/common/service_message.ts @@ -0,0 +1,20 @@ +import { wrt } from '../browser/wrt'; + +let foregroundAppMessagePort: any = null; +let lastForegroundApp: string = ''; + +export function notifyServiceMessage(type: string, message: string) { + let foregroundApp = wrt.tv?.getForegroundApp(); + if (!foregroundApp || foregroundApp == 'none') + return; + + try { + if (!foregroundAppMessagePort || lastForegroundApp != foregroundApp) { + foregroundAppMessagePort = + global.tizen.messageport.requestRemoteMessagePort(foregroundApp, 'wrt.message.port'); + lastForegroundApp = foregroundApp; + } + if (foregroundAppMessagePort) + foregroundAppMessagePort.sendMessage([{key: type, value: ['', message]}]); + } catch { } +} diff --git a/wrt_app/service/device_api_router.ts b/wrt_app/service/device_api_router.ts index afbd161..eed2979 100644 --- a/wrt_app/service/device_api_router.ts +++ b/wrt_app/service/device_api_router.ts @@ -1,4 +1,5 @@ import { wrt } from '../browser/wrt'; +import * as ServiceMessage from '../common/service_message'; export class DeviceAPIRouter { currentApplication: any; @@ -101,6 +102,7 @@ export class DeviceAPIRouter { } global.webapis.mde.updateRemoteInput = (inputString: string) => { mde.updateRemoteInput(inputString); + ServiceMessage.notifyServiceMessage('remote-input', inputString); } global.webapis.mde.selectRemoteInput = () => { mde.selectRemoteInput(); -- 2.7.4 From 87546938930b8d7f30cc3f5a686d019a77ff809f Mon Sep 17 00:00:00 2001 From: DongHyun Song Date: Mon, 14 Mar 2022 15:12:47 +0900 Subject: [PATCH 02/16] [Service] Debugging condole.log with ServiceMessage There is no way to get the console message with commercial products because Tizen SDK cannot get the 'dlog' actually. - 'dlog' is disabled on release firmware. Thus, this way provides a new way to catch the console message of service application by UI application's message port - local message port 'wrt.message.port' - message format : {'service-log' : message } Change-Id: Ia42dbecb9daa492ffe58b4b5550d804569190232 Signed-off-by: DongHyun Song --- wrt_app/common/service_message.ts | 31 +++++++++++++++++++++++++++++-- wrt_app/service/service_runner.ts | 13 +++++-------- 2 files changed, 34 insertions(+), 10 deletions(-) diff --git a/wrt_app/common/service_message.ts b/wrt_app/common/service_message.ts index 4080701..33ceb3b 100644 --- a/wrt_app/common/service_message.ts +++ b/wrt_app/common/service_message.ts @@ -1,7 +1,9 @@ import { wrt } from '../browser/wrt'; +let funcConsoleLog: any = null; let foregroundAppMessagePort: any = null; let lastForegroundApp: string = ''; +const wrtCommonPort = 'wrt.message.port'; export function notifyServiceMessage(type: string, message: string) { let foregroundApp = wrt.tv?.getForegroundApp(); @@ -11,10 +13,35 @@ export function notifyServiceMessage(type: string, message: string) { try { if (!foregroundAppMessagePort || lastForegroundApp != foregroundApp) { foregroundAppMessagePort = - global.tizen.messageport.requestRemoteMessagePort(foregroundApp, 'wrt.message.port'); + global.tizen.messageport.requestRemoteMessagePort(foregroundApp, wrtCommonPort); lastForegroundApp = foregroundApp; } if (foregroundAppMessagePort) - foregroundAppMessagePort.sendMessage([{key: type, value: ['', message]}]); + foregroundAppMessagePort.sendMessage([{ key: type, value: ['', message] }]); } catch { } } + +export function initConsoleMessageNotification(id: string) { + try { + let mainAppId = wrt.getMainAppId(id); + if (!mainAppId) + return; + + let mainAppMessagePort = global.tizen.messageport.requestRemoteMessagePort( + mainAppId, wrtCommonPort); + if (!mainAppMessagePort) + return; + + Object.defineProperty(global, 'mainAppMessagePort', + { value: mainAppMessagePort, writable: false }); + + funcConsoleLog = console.log; + console.log = (log: any) => { + funcConsoleLog(log); + if (global.mainAppMessagePort) { + let value = [id, log.toString()]; + global.mainAppMessagePort.sendMessage([{ key: 'service-log', value }]); + } + } + } catch { } +} \ No newline at end of file diff --git a/wrt_app/service/service_runner.ts b/wrt_app/service/service_runner.ts index c7e40d2..405c9e9 100644 --- a/wrt_app/service/service_runner.ts +++ b/wrt_app/service/service_runner.ts @@ -3,11 +3,7 @@ import * as XWalkExtension from '../common/wrt_xwalk_extension'; import { DeviceAPIRouter } from './device_api_router'; import { isMainThread, parentPort, workerData } from 'worker_threads'; import { wrt } from '../browser/wrt'; - -Object.defineProperty(global, 'serviceType', { - value: wrt.getServiceModel(), - writable: false -}); +import * as ServiceMessage from '../common/service_message'; function isServiceApplication() { return global['serviceType'] !== 'UI'; @@ -90,6 +86,7 @@ export function start(id: string, filename: string) { console.debug(`serviceType : ${global['serviceType']}`) new DeviceAPIRouter(id, isGlobalService()); printAppControlData(id); + ServiceMessage.initConsoleMessageNotification(id); // This is for awaking up uv loop. if (isGlobalService()) { @@ -146,9 +143,9 @@ function run() { process.exit(); } - Object.defineProperty(global, 'internalId', { - value: id, - writable: false + Object.defineProperties(global, { + 'internalId': { value: id, writable: false }, + 'serviceType': { value: wrt.getServiceModel(), writable: false } }); let filename = workerData.filename; -- 2.7.4 From 3f3746a295456a15adf97f49ce18fa4e5ef1eb2b Mon Sep 17 00:00:00 2001 From: "yman.son" Date: Fri, 25 Mar 2022 16:51:11 +0900 Subject: [PATCH 03/16] [VD] resolve metadata-profile.xml parsing error if use the metadata sample used for config.xml, need add xmlns information to the profile. Change-Id: Ic935970691f3ca5b4148521e868858255c33c816 Signed-off-by: yman.son --- packaging/metadata-profile.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packaging/metadata-profile.xml b/packaging/metadata-profile.xml index 8eb87d7..bb68518 100755 --- a/packaging/metadata-profile.xml +++ b/packaging/metadata-profile.xml @@ -1,5 +1,5 @@ - + Y -- 2.7.4 From 124042b9cc4c28c514d218a1b92e5feb239baf1d Mon Sep 17 00:00:00 2001 From: DongHyun Song Date: Wed, 23 Mar 2022 09:46:07 +0900 Subject: [PATCH 04/16] [Service] Unset post callbacks Unexpectedly, post callbacks can be fired from webapi worker thread after its wrt::api::XwalkExtension instance was destroyed. Thus, this will unset the post callbacks on unloadInstance API. Related chromium-efl patch: https://review.tizen.org/gerrit/272695/ Change-Id: I82e5bdfc48bed4b4d208ca6dd485bdffd1c31a0d Signed-off-by: DongHyun Song --- wrt_app/common/wrt_xwalk_extension.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/wrt_app/common/wrt_xwalk_extension.ts b/wrt_app/common/wrt_xwalk_extension.ts index 4e32394..c0c9971 100644 --- a/wrt_app/common/wrt_xwalk_extension.ts +++ b/wrt_app/common/wrt_xwalk_extension.ts @@ -230,6 +230,9 @@ export const setRuntimeMessageHandler = (handler: (type: string, data?: string, } export let cleanup = () => { + for (const name in extensions_) { + extensions_[name].unloadInstance(); + } delete global.tizen; instance = undefined; } -- 2.7.4 From dab685448500e096a504ba992755513502307b8e Mon Sep 17 00:00:00 2001 From: liwei Date: Wed, 13 Apr 2022 17:22:11 +0800 Subject: [PATCH 05/16] [VD] Send appcontrol data to app in 'ResumeWithAppControl' Send appcontrol data to app side when appcontrol data 'ResumeWithAppControl =Yes',then app can use the key/value flexibility. Change-Id: Id3101bf03c1fc35885e08871c582ea7d4bd7dea8 Signed-off-by: liwei --- wrt_app/src/tv/web_application_tv.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/wrt_app/src/tv/web_application_tv.ts b/wrt_app/src/tv/web_application_tv.ts index 65e7499..d3668fc 100644 --- a/wrt_app/src/tv/web_application_tv.ts +++ b/wrt_app/src/tv/web_application_tv.ts @@ -219,6 +219,7 @@ Then you can get profile log from the initial loading.`; handleAppControlEvent(appControl: any) { this.launchMode = appControl.getData('http://samsung.com/appcontrol/data/launch_mode'); this.preloadStatus = 'none'; + let resumeWithAppControl = appControl.getData('ResumeWithAppControl'); if (this.launchMode === 'runningAsBackground') { this.webApplication.suspended = false; @@ -226,7 +227,7 @@ Then you can get profile log from the initial loading.`; this.webApplication.windowList[this.webApplication.windowList.length - 1].hide(); this.webApplication.sendAppControlEvent(); return false; - } else if (this.launchMode === 'runningAsForeground') { + } else if ((this.launchMode === 'runningAsForeground') || (resumeWithAppControl === 'Yes')) { this.webApplication.resume(); this.webApplication.sendAppControlEvent(); return false; -- 2.7.4 From 13666a518c90d576902f50a63755cb5b348de839 Mon Sep 17 00:00:00 2001 From: DongHyun Song Date: Wed, 20 Apr 2022 16:16:45 +0900 Subject: [PATCH 06/16] [DeviceHome][VD] Disable to build DeviceHome DeviceHome will be managed by TV app store as a downloadable app. TV store ID : 3202204027208 TV profile don't need to build and install DeviceHome on wrtjs side. Change-Id: I1c13524849b8bb08a2986171769ec414c7639dff Signed-off-by: DongHyun Song --- packaging/wrtjs.spec | 15 +++------------ 1 file changed, 3 insertions(+), 12 deletions(-) diff --git a/packaging/wrtjs.spec b/packaging/wrtjs.spec index e605fba..95842fe 100644 --- a/packaging/wrtjs.spec +++ b/packaging/wrtjs.spec @@ -194,18 +194,11 @@ cp -r %{app_dir}/* %{buildroot}%{_resourcedir}/ echo "No TPK generation" %endif -%if 0%{?_use_d2d} -%if "%{?tizen_profile_name}" == "tv" - %define _d2d_app_file_name device_home.tmg - %define _d2d_app_extension tmg - %define _d2d_install_path %{TZ_SYS_DATA}/device_home - install -m 0644 packaging/config_tv.xml.in device_home/config.xml -%else +%if "%{?tizen_profile_name}" != "tv" && 0%{?_use_d2d} %define _d2d_app_file_name device_home.wgt %define _d2d_app_extension wgt %define _d2d_install_path %{_appdir}/.preload-rw-wgt install -m 0644 packaging/config.xml.in device_home/config.xml -%endif %if 0%{?_use_d2d_offload} install -m 0644 key.pem device_home/signaling_server/gen/ install -m 0644 cert.pem device_home/signaling_server/gen/ @@ -261,12 +254,10 @@ else fi %post -%if "%{?_local_build}" == "1" -%if 0%{?_use_d2d} +%if "%{?_local_build}" == "1" && "%{?tizen_profile_name}" != "tv" && 0%{?_use_d2d} pkgcmd -un 9z6IujVul3 pkgcmd -i -t wgt -p %{_d2d_install_path}/%{_d2d_app_file_name} %endif -%endif %postun @@ -276,10 +267,10 @@ rm -fr %{buildroot} %files %manifest packaging/wrtjs.manifest %license LICENSE +%if "%{?tizen_profile_name}" != "tv" %if 0%{?_use_d2d} %{_d2d_install_path}/%{_d2d_app_file_name} %endif -%if "%{?tizen_profile_name}" != "tv" %caps(cap_setgid,cap_sys_admin=ei) %{_bindir}/wrt-loader %else %{_bindir}/wrt-loader -- 2.7.4 From ae6a06f318887167255a3d599a5d23b5a200d525 Mon Sep 17 00:00:00 2001 From: DongHyun Song Date: Wed, 20 Apr 2022 16:07:51 +0900 Subject: [PATCH 07/16] [DeviceHome][VD] Fix setting the 'wsa' path The start page includes 'wsa' path, then replace the base path as /res/wsa/client/ for tmg app. Change-Id: Iaed061140e26ee04a78402fb1879bc861f7cb4a4 Signed-off-by: DongHyun Song --- device_home/service/service.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/device_home/service/service.js b/device_home/service/service.js index 138dd23..4cf8593 100755 --- a/device_home/service/service.js +++ b/device_home/service/service.js @@ -357,7 +357,7 @@ var HTTPserverStart = function() { if (is_tv) { platform_app_path = '/opt/usr/apps'; - if (!fs.existsSync(path.join(__dirname, platform_client_res_path))) { + if (__dirname.indexOf('/wsa/') > -1) { platform_client_res_path = '/res/wsa/client'; } console.log(`${TAG} TV Profile`); -- 2.7.4 From 6f6889b08c4c0048400137df04854573f8f798d5 Mon Sep 17 00:00:00 2001 From: DongHyun Song Date: Mon, 18 Apr 2022 17:00:45 +0900 Subject: [PATCH 08/16] [DeviceHome] Seperate TV service with service_tv.js 1. Add new file service_tv.js for TV product features 2. Remove unnecessary service.js symlink 3. Support webapis.getProfile() Change-Id: Ied35583ff74d0b8b864e65c091a8791ffe0f31f9 Signed-off-by: DongHyun Song --- device_home/service.js | 15 ------ device_home/service/service.js | 7 ++- device_home/service/tv/service_tv.js | 102 +++++++++++++++++++++++++++++++++++ packaging/config.xml.in | 2 +- packaging/config_tv.xml.in | 2 +- wrt_app/service/device_api_router.ts | 6 +++ 6 files changed, 116 insertions(+), 18 deletions(-) delete mode 100644 device_home/service.js create mode 100644 device_home/service/tv/service_tv.js diff --git a/device_home/service.js b/device_home/service.js deleted file mode 100644 index 17cd727..0000000 --- a/device_home/service.js +++ /dev/null @@ -1,15 +0,0 @@ -const deviceHome = require('./service/service'); - -module.exports.onStart = async function() { - deviceHome.onStart(); - // Temporarily remove the signaling server - // require('./signaling_server/gen/app'); -}; - -module.exports.onStop = function() { - deviceHome.onStop(); -}; - -module.exports.onRequest = function() { - deviceHome.onRequest(); -} diff --git a/device_home/service/service.js b/device_home/service/service.js index 138dd23..221a2ee 100755 --- a/device_home/service/service.js +++ b/device_home/service/service.js @@ -26,7 +26,7 @@ const TAG = '[DeviceHome][service.js]' const TIZEN_WEB_APP_SHARED_RESOURCES = 'shared/res/'; const WEBCLIP_DIRECTORY = 'webclip'; const WEBCLIP_MANIFEST = 'manifest.json'; -const is_tv = webapis.cachedProperty !== undefined; +const is_tv = webapis.getProfile() === 'TV'; const local_ip = '127.0.0.1'; const non_ip_list = [ '1', @@ -529,6 +529,11 @@ var HTTPserverStart = function() { webapis.mde.launchBrowserFromUrl(req.body.url); }); + if (is_tv) { + const tvService = require('./tv/service_tv'); + tvService.initialize(app); + } + httpserver = http.createServer(app); httpserver.listen(g.port, function() { console.log(`Device home is running on port ${g.port}`); diff --git a/device_home/service/tv/service_tv.js b/device_home/service/tv/service_tv.js new file mode 100644 index 0000000..cc0156c --- /dev/null +++ b/device_home/service/tv/service_tv.js @@ -0,0 +1,102 @@ +const express = require('express'); + +let virtualKeyGeneratorInitialized = false; +let virtualMouseInitialized = false; +const keyMap = { + 'Ambient': 530, + 'ArrowLeft': 113, + 'ArrowRight': 114, + 'ArrowUp': 111, + 'ArrowDown': 116, + 'Back': 9, + 'ChannelDown': 95, + 'ChannelUp': 96, + 'HomeUI': 71, + 'OK': 36, + 'VolumeDown': 75, + 'VolumeUp': 76, + 'WebBrowser': 158, +}; + +let webapis = global.webapis; + +function initializeVirtualKeyGenerator() { + if (!virtualKeyGeneratorInitialized) { + virtualKeyGeneratorInitialized = true; + webapis.mde.initVirtualEventGenerator(0); + } + return virtualKeyGeneratorInitialized; +} + +function initializeVirtualMouse() { + if (!virtualMouseInitialized) { + virtualMouseInitialized = true; + webapis.mde.initVirtualEventGenerator(1); + } + return virtualMouseInitialized; +} + +module.exports.initialize = (app) => { + app.post('/sendKey', express.json(), (req, res) => { + if (!initializeVirtualKeyGenerator()) { + res.send({ + result: 'mde API is not enabled' + }); + return; + } + + var keyName = req.body.keyName; + let keyCode = keyMap[keyName]; + console.log(`keyName : ${keyName}, keyCode : ${keyCode}`); + webapis.mde.generateVirtualKeyEvent(keyCode, 2); + res.send({ + result: 'ok' + }); + }); + + app.post('/sendString', express.json(), (req, res) => { + var inputString = req.body.inputString; + console.log(`inputString : ${inputString}`); + webapis.mde.updateRemoteInput(inputString); + res.send({ + result: 'ok' + }); + }); + + app.post('/selectString', express.json(), (req, res) => { + webapis.mde.selectRemoteInput(); + res.send({ + result: 'ok' + }); + }); + + app.post('/mouseMove', express.json(), (req, res) => { + if (!initializeVirtualMouse()) { + res.send({ + result: 'mde API is not enabled' + }); + return; + } + var dx = req.body.dx; + var dy = req.body.dy; + console.log(`dx : ${dx}, dy : ${dy}`); + webapis.mde.generateVirtualMouseMoveEvent(dx, dy, 3); + res.send({ + result: 'ok' + }); + }); + + app.post('/previewData', express.json(), (req, res) => { + let pkgId = req.body.pkgId; + let previewData = '{ sections: [] }'; + if (webapis && webapis.getPreviewData) { + previewData = webapis.getPreviewData(pkgId); + } else { + console.error('webapis.getPreviewData is unsupported.'); + } + res.send({ + result: JSON.parse(previewData) + }); + }); + +} \ No newline at end of file diff --git a/packaging/config.xml.in b/packaging/config.xml.in index 2b80c64..e2a99de 100644 --- a/packaging/config.xml.in +++ b/packaging/config.xml.in @@ -14,7 +14,7 @@ - + DeviceHomeService DeviceHomeService diff --git a/packaging/config_tv.xml.in b/packaging/config_tv.xml.in index cf869c3..af85a01 100644 --- a/packaging/config_tv.xml.in +++ b/packaging/config_tv.xml.in @@ -14,7 +14,7 @@ - + DeviceHomeService DeviceHomeService diff --git a/wrt_app/service/device_api_router.ts b/wrt_app/service/device_api_router.ts index eed2979..40c33d7 100644 --- a/wrt_app/service/device_api_router.ts +++ b/wrt_app/service/device_api_router.ts @@ -68,12 +68,18 @@ export class DeviceAPIRouter { return { images: 'no' }; } } + global.webapis.getProfile = () => { + if (wrt.tv) return 'TV'; + else if (wrt.da) return 'DA'; + else return 'common'; + } Object.defineProperties(global.webapis, { getCallerAppId: { writable: false, enumerable: true }, getPackageId: { writable: false, enumerable: true }, getServiceId: { writable: false, enumerable: true }, postPlainNotification: { writable: false, enumerable: true }, getPreviewData: { writable: false, enumerable: true }, + getProfile: { writable: false, enumerable: true }, }); this.initMDEWebapis(); this.initEdgeWebapis(); -- 2.7.4 From b83469e0acda7d1d9e3f11f84f2a403e4b8b9a89 Mon Sep 17 00:00:00 2001 From: Surya Kumar Date: Mon, 25 Apr 2022 20:16:36 +0530 Subject: [PATCH 09/16] [DeviceHome] Fix crash due to empty appid Platform team has reported crashes on wrt calling webapis with empty appid, which happens on installation of resource packages without app. This change safeguards such instances. Change-Id: Ied8e3f6f7253aa1bc38421e296842c8c09d54e6c Signed-off-by: Surya Kumar --- device_home/service/service.js | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/device_home/service/service.js b/device_home/service/service.js index 7cd039d..1bedb6f 100755 --- a/device_home/service/service.js +++ b/device_home/service/service.js @@ -200,6 +200,10 @@ function getWebclipsManifest() { function setPackageInfoEventListener() { const packageEventCallback = { oninstalled: async function(packageInfo) { + if (typeof(packageInfo.name) !== 'string' || !packageInfo.name.length) { + console.debug(`${TAG} Package with no appid is installed`); + return; + } console.log(`${TAG} The package ${packageInfo.name} is installed`); const app = addD2Ddata(packageInfo.id, packageInfo.appIds[0], packageInfo.name, packageInfo.iconPath); if (app.path !== undefined) { @@ -216,9 +220,17 @@ function setPackageInfoEventListener() { } }, onupdated: function(packageInfo) { + if (typeof(packageInfo.name) !== 'string' || !packageInfo.name.length) { + console.debug(`${TAG} Package with no appid is updated`); + return; + } console.log(`${TAG} The package ${packageInfo.name} is updated`); }, onuninstalled: function(packageId) { + if (typeof(packageId) !== 'string' || !packageId.length) { + console.debug(`${TAG} Package with no appid is uninstalled`); + return; + } console.log(`${TAG} The package ${packageId} is uninstalled`); removeD2Ddata(packageId); evtEmit.emit('updateapplist', 'message', dataApps); -- 2.7.4 From 88beccdf6e56f7c26b7ccd5be4679fbb7cbb0db0 Mon Sep 17 00:00:00 2001 From: DongHyun Song Date: Thu, 21 Apr 2022 20:06:18 +0900 Subject: [PATCH 10/16] [DeviceHome][VD] Add 'action' parameter for deeplink If there is 'action' parameter, then, it will be a 'PAYLOAD' data for deeplink. Change-Id: Ic7f3414364945f0a46b2e74274924cfcdc8a9b31 Signed-off-by: DongHyun Song --- device_home/client/js/actions.js | 25 ++++++++----------------- device_home/service/app_proxy.js | 40 +++++++++++++++++++++++++++------------- 2 files changed, 35 insertions(+), 30 deletions(-) mode change 100755 => 100644 device_home/client/js/actions.js mode change 100755 => 100644 device_home/service/app_proxy.js diff --git a/device_home/client/js/actions.js b/device_home/client/js/actions.js old mode 100755 new mode 100644 index fdcb1c5..1ff3f9a --- a/device_home/client/js/actions.js +++ b/device_home/client/js/actions.js @@ -11,18 +11,13 @@ class Actions { * @returns {Function} */ - launchAppOnTV(pkgId, appId, callback) { - const xhr = new XMLHttpRequest(); - - var self = this; - var retFunc = function() { - var data = { - pkgId: pkgId, - appId: appId - }; - self.sendDataToApp(pkgId, appId, data, callback); + launchAppOnTV(pkgId, appId, action, callback) { + var data = { + pkgId, + appId, + action }; - return retFunc; + return this.sendDataToApp('app', data, callback); }; /** @@ -32,12 +27,8 @@ class Actions { * @param {Object} data * @param {Function} callback */ - sendDataToApp(pkgId, appId, data, callback) { + sendDataToApp(api, data, callback) { const xhr = new XMLHttpRequest(); - // add tv app id - data.appId = appId; - data.pkgId = pkgId; - xhr.onreadystatechange = function() { if (xhr.readyState === xhr.DONE) { if (xhr.status === 200 || xhr.status === 201) { @@ -50,7 +41,7 @@ class Actions { } } } - xhr.open('POST', serverURL + ':' + serverPort + '/app'); + xhr.open('POST', `${serverURL}:${serverPort}/${api}`); xhr.setRequestHeader('Content-Type', 'application/json'); xhr.send(JSON.stringify(data)); }; diff --git a/device_home/service/app_proxy.js b/device_home/service/app_proxy.js old mode 100755 new mode 100644 index 1cd814c..d71fca1 --- a/device_home/service/app_proxy.js +++ b/device_home/service/app_proxy.js @@ -6,7 +6,7 @@ var appRouters = []; var path = null; var currentD2DAppId = null; -function runApp(appId, port, callback) { +function runApp(appId, port, action, callback) { function onRunningAppsContext(contexts) { var isRunning = false; for (var i = 0; i < contexts.length; i++) { @@ -16,22 +16,35 @@ function runApp(appId, port, callback) { } } - if (isRunning && currentD2DAppId === appId) { + if (isRunning && currentD2DAppId === appId && !action) { callback(); } else { const urlParam = require('./service').getUrlParam(); const urlObj = url.parse(urlParam, true).query; urlObj.target = is_tv ? 'tv' : 'fhub'; - const appControl = new tizen.ApplicationControl( - "http://tizen.org/appcontrol/operation/default", null, null, null, - [new tizen.ApplicationControlData( - "http://tizen.org/appcontrol/data/launch_port", [port] - ), - new tizen.ApplicationControlData( - "http://tizen.org/appcontrol/data/url_parameter", [JSON.stringify(urlObj)] - )] - ); - + var appControl; + if (action) { + console.log('action : ' + JSON.stringify(action)); + appControl = new tizen.ApplicationControl( + "http://tizen.org/appcontrol/operation/eden_resume", null, null, null, + [new tizen.ApplicationControlData( + "PAYLOAD", [JSON.stringify({ values: action }) ] + ), + new tizen.ApplicationControlData( + "SkipReload", ['No'] + )] + ); + } else { + appControl = new tizen.ApplicationControl( + "http://tizen.org/appcontrol/operation/default", null, null, null, + [new tizen.ApplicationControlData( + "http://tizen.org/appcontrol/data/launch_port", [port] + ), + new tizen.ApplicationControlData( + "http://tizen.org/appcontrol/data/url_parameter", [JSON.stringify(urlObj)] + )] + ); + } currentD2DAppId = appId; tizen.application.launchAppControl(appControl, appId, callback); } @@ -56,6 +69,7 @@ module.exports = function (app, port) { path = req.body.pkgId ? req.body.pkgId : path; var appId = req.body.appId; var pkgId = req.body.pkgId; + var action = req.body.action; var name = appId.split(".")[1]; var appRouter = appRouters.filter(function (router) { return router.path === path; @@ -71,7 +85,7 @@ module.exports = function (app, port) { } console.log('[GlobalWebServer] appProxy.post ', path, action); // run app - runApp(appId, port, function () { + runApp(appId, port, action, function () { res.send({ port: port }); }); } -- 2.7.4 From f32af3b76102b15639668917138605f037698a7f Mon Sep 17 00:00:00 2001 From: DongHyun Song Date: Thu, 21 Apr 2022 20:10:38 +0900 Subject: [PATCH 11/16] [DeviceHome][VD] Support preview display on client When there are d2dservice apps having preview data, - 'click' event is for listing preview data - 'dblclick' event is for opening the UI offloading page For the deeplink, when the preview icon is clicked on client side, then it will request appcontrol with 'action' parameter as 'PAYLAOAD' Parent patch: https://review.tizen.org/gerrit/274112/ Change-Id: I8c367802aed101644f6d086a9e30ae9dcc6f7eae Signed-off-by: DongHyun Song --- device_home/client/client.html | 5 ++ device_home/client/css/style.css | 13 ++++++ device_home/client/js/myApps.js | 98 ++++++++++++++++++++++++++++++++-------- 3 files changed, 96 insertions(+), 20 deletions(-) mode change 100755 => 100644 device_home/client/client.html mode change 100755 => 100644 device_home/client/css/style.css mode change 100755 => 100644 device_home/client/js/myApps.js diff --git a/device_home/client/client.html b/device_home/client/client.html old mode 100755 new mode 100644 index 025676a..e6c5461 --- a/device_home/client/client.html +++ b/device_home/client/client.html @@ -53,6 +53,11 @@
+
+
+
+
+
My Device App
diff --git a/device_home/client/css/style.css b/device_home/client/css/style.css old mode 100755 new mode 100644 index 3e7b5eb..56afa8b --- a/device_home/client/css/style.css +++ b/device_home/client/css/style.css @@ -50,6 +50,7 @@ box-sizing: border-box; width: 100%; } + .app-dummy-payment input.ui-inline { width: auto; display: inline; @@ -74,3 +75,15 @@ body { .app-display-none { display: none; } + +.app-preview-img { + width: 100px; +} + +#preview-section { + display: none; +} + +#preview-list { + display: none; +} \ No newline at end of file diff --git a/device_home/client/js/myApps.js b/device_home/client/js/myApps.js old mode 100755 new mode 100644 index 167feb9..869db93 --- a/device_home/client/js/myApps.js +++ b/device_home/client/js/myApps.js @@ -24,7 +24,6 @@ const NEW_WINDOW_TIMEOUT = 1000; const myappsmodule = {}; (function () { - var xhr; function emptyElement(elm) { while (elm.firstChild) { elm.removeChild(elm.firstChild); @@ -45,6 +44,26 @@ const myappsmodule = {}; }, NEW_WINDOW_TIMEOUT); }; + function request(method, json, api, body) { + return new Promise( + (resolve, reject) => { + const xhr = new XMLHttpRequest(); + xhr.onreadystatechange = function () { + if (xhr.readyState === xhr.DONE) { + if (xhr.status === 200 || xhr.status === 201) { + resolve(xhr.responseText); + } else { + reject(xhr.responseText); + } + } + }; + xhr.open(method, `${serverURL}:${serverPort}/${api}`); + if (json) + xhr.setRequestHeader('Content-Type', 'application/json;charset=utf-8'); + body ? xhr.send(body) : xhr.send(); + }); + } + function showListView(dataArray) { var formResult = document.getElementById("d2dApps"), imgResult = document.getElementById("d2dAppList"), @@ -71,24 +90,37 @@ const myappsmodule = {}; d2dApp = dataArray[i]['d2dApp']; if (d2dApp.hasOwnProperty("appName")) { if (d2dApp.iconPath) { - icon = d2dApp.iconPath.substring(d2dApp.iconPath.indexOf('/',10)+1); + icon = d2dApp.iconPath.substring(d2dApp.iconPath.indexOf('/', 10) + 1); imgObj.src = `/d2dIcon/${icon}`; } else { imgObj.src = `./images/icon.png`; } imgObj.className = "app-icon-img"; imgObj.alt = d2dApp.appName; + imgObj.setAttribute('pkgId', d2dApp.pkgId); + imgObj.setAttribute('appId', d2dApp.appId); textObj.style.display = "block"; textObj.style.margin = "0 auto"; textObj.style.fontSize = "14px"; textObj.innerHTML = d2dApp.appName; } - imgObj.addEventListener("click", actions.launchAppOnTV( - d2dApp.pkgId, - d2dApp.appId, - function (response) { - openAppWindow(response); - })); + imgObj.addEventListener("click", function () { + var pkgId = this.getAttribute('pkgId'); + var appId = this.getAttribute('appId'); + showPreview(pkgId, appId); + }, false); + imgObj.addEventListener("dblclick", function () { + var pkgId = this.getAttribute('pkgId'); + var appId = this.getAttribute('appId'); + actions.launchAppOnTV( + pkgId, + appId, + '', + function (response) { + // TODO: should check if the app provides client page or not + openAppWindow(response); + }) + }, false); formObj.appendChild(imgObj); formObj.appendChild(textObj); @@ -103,19 +135,45 @@ const myappsmodule = {}; } } + function showPreview(pkgId, appId) { + const reqBody = JSON.stringify({ pkgId, appId }); + request('POST', true, 'previewData', reqBody).then((body) => { + const preview = JSON.parse(body); + if (!preview.result.sections || !preview.result.sections.length) + return; + const previewSection = document.getElementById('preview-section'); + previewSection.style.display = 'block'; + previewSection.innerHTML = 'Preview'; + const previewList = document.getElementById('preview-list'); + previewList.style.display = 'grid'; + previewList.innerHTML = ''; + preview.result.sections.forEach(section => { + if (!section.tiles) + return; + section.tiles.forEach(tile => { + if (!tile.image_url) + return; + const img = document.createElement('img'); + img.src = tile.image_url; + img.border = 0; + img.className = 'app-preview-img'; + img.onclick = function () { + actions.launchAppOnTV( + pkgId, + appId, + tile.action_play_url, + function () { console.log('preview resumed') }); + } + previewList.appendChild(img); + }); + }); + }); + } + function showList() { - xhr = new XMLHttpRequest(); - xhr.onreadystatechange = function () { - if (xhr.readyState === xhr.DONE) { - if (xhr.status === 200 || xhr.status === 201) { - showListView(JSON.parse(xhr.responseText)); - } else { - console.error(xhr.responseText); - } - } - }; - xhr.open('GET', serverURL + ':' + serverPort + '/appList'); - xhr.send(); + request('GET', false, 'appList').then((body) => { + showListView(JSON.parse(body)); + }) } function init() { -- 2.7.4 From c044daf366a7a6d55afe7ffea3d9fb13bc75122e Mon Sep 17 00:00:00 2001 From: Hunseop Jeong Date: Tue, 26 Apr 2022 16:50:50 +0900 Subject: [PATCH 12/16] [SignalingServer] Enable the d2d_offload flag Update the signaling server code and define the service type for the signaling server to run the service independently. Change-Id: Ib69f48392b00734231a8f0f2c35f0feb87f75c12 Signed-off-by: Hunseop Jeong --- device_home/signaling_server/gen/app.js | 377 +-------------------- device_home/signaling_server/gen/edge.js | 6 +- device_home/signaling_server/gen/service.js | 409 +++++++++++++++++++++++ device_home/signaling_server/gen/socket-tizen.js | 2 +- device_home/signaling_server/gen/util.js | 4 +- packaging/config.xml.in | 5 + packaging/wrtjs.spec | 1 + 7 files changed, 423 insertions(+), 381 deletions(-) create mode 100644 device_home/signaling_server/gen/service.js diff --git a/device_home/signaling_server/gen/app.js b/device_home/signaling_server/gen/app.js index 44998f0..8b0a0a8 100644 --- a/device_home/signaling_server/gen/app.js +++ b/device_home/signaling_server/gen/app.js @@ -1,376 +1,3 @@ -const path = require('path'); -const fs = require('fs'); -const express = require('express'); -const QRCode = require('qrcode'); -const Edge = require('./edge'); -const SocketTizen = require('./socket-tizen'); -const { getMyAddress } = require('./util'); +const { onStart } = require('./service'); -const TAG = 'app.js'; - -const app = express(); - -const options = { - key: fs.readFileSync(path.resolve(__dirname, 'key.pem')), - cert: fs.readFileSync(path.resolve(__dirname, 'cert.pem')) -}; - -console.log(TAG, `platform : ${process.platform}`); - -const httpPort = process.env.HTTP_PORT || 9559; -const httpsPort = process.env.PORT || process.env.HTTPS_PORT || 5443; -const httpsServer = require('https').createServer(options, app); -const httpServer = require('http').createServer(app); -const io = require('socket.io')(); -const isTizen = process.platform === 'tizen'; -const supportMessagePort = isTizen; -// Implementation for edge orchestration -const supportEdgeOrchestration = - typeof webapis !== 'undefined' && webapis.hasOwnProperty('edge'); -console.log(TAG, `supportEdgeOrchestration : ${supportEdgeOrchestration}`); - -io.attach(httpServer); -io.attach(httpsServer); - -const clients = new Set(); -const workers = new Map(); -const sockets = new Map(); -let edgeForCastanets = null; -let forceQuitTimer = null; -let isMeerkatStarted = false; - -app.set('host', '0.0.0.0'); - -if (isTizen) { - app.use(express.static(path.join(__dirname, './public'))); -} else { - app.use( - '/offload.html', - express.static(path.join(__dirname, '../../sample/offload.html')) - ); - // Host offload-worker - app.use( - '/offload-worker', - express.static(path.join(__dirname, '../../offload-worker/src/')) - ); - app.use( - '/offload-worker/offload-worker.js', - express.static(path.join(__dirname, '../../build/offload-worker.js')) - ); - const serveIndex = require('serve-index'); - app.use( - '/', - express.static(path.join(__dirname, '../../build')), - express.static(path.join(__dirname, '../../sample')), - serveIndex(path.join(__dirname, '../../sample')) - ); - app.use( - '/test', - express.static(path.join(__dirname, '../../test')), - serveIndex(path.join(__dirname, '../../test')) - ); -} - -function onConnection(socket) { - if (isTizen && !isMeerkatStarted) { - try { - console.log(TAG, `Try to start Meerkat client.`); - tizen.application.launch( - 'org.tizen.meerkat.client', - () => { - console.log(TAG, `Meerkat client is started.`); - isMeerkatStarted = true; - }, - err => console.error(TAG, 'Failed to launch Meerkat client. ' + err) - ); - } catch (err) { - console.error(TAG, 'Failed to launch Meerkat client. ' + err); - } - } - console.log(TAG, `connection from '${socket.id}.`); - sockets.set(socket.id, socket); - - // client creates a session. - socket.on('create', async function () { - if (forceQuitTimer !== null) { - clearTimeout(forceQuitTimer); - } - - if (clients.has(socket.id)) { - console.log(TAG, `already created by ${socket.id}.`); - return; - } - clients.add(socket.id); - - let qr = null; - const myAddress = getMyAddress(); - if (myAddress) { - try { - qr = await QRCode.toDataURL( - 'https://' + myAddress + ':5443/offload-worker.html' - ); - } catch (err) { - console.error(TAG, 'unabled to generate QR: ' + error); - } - } - - socket.emit('greeting', { - qrCode: qr, - workers: Array.from(workers) - }); - - console.log( - TAG, - `[client] session created by ${socket.id}. workers.size : ${workers.size}` - ); - - if (supportEdgeOrchestration) { - socket.emit( - 'capabilities', - Array.from(edgeForCastanets.getCapabilities()) - ); - } - }); - - socket.on('getcapabilities', function () { - console.log(TAG, `getcapabilities`); - if (supportEdgeOrchestration) { - socket.emit( - 'capabilities', - Array.from(edgeForCastanets.getCapabilities()) - ); - } else { - socket.emit('capabilities', []); - } - }); - - socket.on('requestService', function (workerId) { - if (supportEdgeOrchestration) { - edgeForCastanets.requestService(workerId); - } - }); - - // new worker has been joined. - socket.on('join', function (worker) { - if (supportEdgeOrchestration) { - let deviceIp = socket.request.connection.remoteAddress; - if (deviceIp.indexOf('::ffff:') !== -1) { - deviceIp = deviceIp.substr(7, deviceIp.length); - } - - if (deviceIp) { - edgeForCastanets.joinDevice(deviceIp); - } - } - - workers.set(worker.id, { - socketId: socket.id, - name: worker.name, - features: worker.features, - mediaDeviceInfos: worker.mediaDeviceInfos, - compute_tasks: 0 - }); - console.log( - TAG, - `worker[${workers.size}] join: '${worker.id}' - '${socket.id}', '${worker.name}'`, - worker.features - ); - - for (const client of clients) { - const clientSocket = sockets.get(client); - clientSocket.emit('worker', { - event: 'join', - workerId: worker.id, - socketId: socket.id, - name: worker.name, - features: worker.features, - mediaDeviceInfos: worker.mediaDeviceInfos - }); - } - }); - - // route message between clients. - socket.on('message', function (data) { - console.log(TAG, `message ${JSON.stringify(data)}`); - let socketId = null; - if (workers.has(data.to)) { - socketId = workers.get(data.to).socketId; - } else if (clients.has(data.to)) { - socketId = data.to; - } - - if (socketId) { - const socket = sockets.get(socketId); - socket.emit('message', data); - } - }); - - socket.on('disconnect', function (reason) { - const socketId = socket.id; - sockets.delete(socketId); - if (clients.has(socketId)) { - console.log(TAG, `[client] session terminated by client: ${socketId}`); - - // broadcast to offload-worker - for (const socket of sockets.values()) { - socket.emit('client', { - event: 'bye', - socketId: socketId - }); - } - clients.delete(socketId); - - if (clients.size === 0) { - if (supportMessagePort) { - closeServer(); - } - forceQuitTimer = setTimeout(function () { - console.log( - TAG, - `All clients are destroyed. Broadcast 'forceQuit' to workers` - ); - for (const socket of sockets.values()) { - socket.emit('client', { - event: 'forceQuit', - socketId: socketId - }); - } - }, 5000); - } - } else { - if (supportEdgeOrchestration) { - let deviceIp = socket.request.connection.remoteAddress; - if (deviceIp.indexOf('::ffff:') !== -1) { - deviceIp = deviceIp.substr(7, deviceIp.length); - } - - if (deviceIp) { - edgeForCastanets.disconnectDevice(deviceIp); - } - } - - let workerId = null; - workers.forEach(function (value, key, map) { - if (value.socketId === socket.id) { - workerId = key; - } - }); - - if (workerId) { - for (const client of clients) { - const socket = sockets.get(client); - socket.emit('worker', { - event: 'bye', - workerId: workerId, - socketId: socket.id - }); - } - workers.delete(workerId); - console.log(TAG, `worker[${workers.size}] bye: '${workerId}'`); - } - } - }); -} - -io.of('/offload-js').on('connection', onConnection); - -if (supportEdgeOrchestration) { - edgeForCastanets = new Edge( - 'castanets', - 'android', - 'com.samsung.android.castanets' - ); -} - -function startServer() { - console.log(TAG, 'starting server...'); - - if (!httpsServer.listening) { - httpsServer.listen(httpsPort, function () { - console.log(TAG, `server is listening on https ${httpsPort} port.`); - }); - } - - if (!httpServer.listening) { - httpServer.listen(httpPort, function () { - console.log(TAG, `server is listening on http ${httpPort} port.`); - }); - } -} - -function closeServer() { - console.log(TAG, 'closing server...'); - - if (httpsServer.listening) { - httpsServer.close(err => { - if (err) { - console.error(`failed to close the https server:`, err); - } - }); - } - - if (httpServer.listening) { - httpServer.close(err => { - if (err) { - console.error(`failed to close the http server:`, err); - } - }); - } -} - -if (supportMessagePort) { - console.log(TAG, 'listening tizen messageport...'); - const localPort = tizen.messageport.requestLocalMessagePort('offload'); - localPort.addMessagePortListener(messages => { - if (messages.length === 0) { - console.error(TAG, 'Not found message'); - return; - } - - const message = messages[0]; - const event = message.key; - const value = JSON.parse(message.value); - const id = value.id; - - if (event === 'connect') { - // FIXME: The message port does not guarantee that the connection has - // been disconnected when the page is reloaded. Therefore, if a new - // connection occurs with the same id, the existing connection is - // disconnected. - if (sockets.has(id)) { - console.log(TAG, `Disconnect already connected socket: ${id}`); - const socket = sockets.get(id); - socket.handleEvents('disconnect'); - } - - const socket = new SocketTizen(id, localPort); - socket.on('connection', onConnection); - socket.connect(); - sockets.set(id, socket); - startServer(); - } else { - const socket = sockets.get(id); - socket.handleEvents(event, value.data); - } - }); - - // Check the client status - function checkConnectionStatus() { - for (const client of clients) { - const socket = sockets.get(client); - if (socket.constructor === SocketTizen) { - try { - socket.emit('status'); - } catch (e) { - console.error(TAG, `Failed to check ${client} status`); - socket.handleEvents('disconnect'); - } - } - } - } - - // Prevent to terminate the process - setInterval(checkConnectionStatus, 1000); -} else { - startServer(); -} +onStart(); diff --git a/device_home/signaling_server/gen/edge.js b/device_home/signaling_server/gen/edge.js index 8573ef2..4751384 100644 --- a/device_home/signaling_server/gen/edge.js +++ b/device_home/signaling_server/gen/edge.js @@ -43,7 +43,7 @@ class Edge { ); if (deviceList === null || deviceList.ipaddrs.length === 0) { - console.log(TAG, `deviceList is null`); + console.log(TAG, 'deviceList is null'); return this._capabilities; } @@ -57,8 +57,8 @@ class Edge { console.log(TAG, `ReadCapability : ${ipaddr}, ${features.capability}`); try { let jsonCapability = JSON.parse(features.capability); - if (jsonCapability.hasOwnProperty('offloadjs')) { - jsonCapability = jsonCapability['offloadjs']; + if (Object.hasOwnProperty.call(jsonCapability, 'offloadjs')) { + jsonCapability = jsonCapability.offloadjs; } this._capabilities.set(jsonCapability.id, { ipaddr: ipaddr, diff --git a/device_home/signaling_server/gen/service.js b/device_home/signaling_server/gen/service.js new file mode 100644 index 0000000..86d51bb --- /dev/null +++ b/device_home/signaling_server/gen/service.js @@ -0,0 +1,409 @@ +const path = require('path'); +const fs = require('fs'); +const express = require('express'); +const QRCode = require('qrcode'); +const Edge = require('./edge'); +const SocketTizen = require('./socket-tizen'); +const { getMyAddress } = require('./util'); + +const TAG = 'service.js'; + +const app = express(); + +const options = { + key: fs.readFileSync(path.resolve(__dirname, 'key.pem')), + cert: fs.readFileSync(path.resolve(__dirname, 'cert.pem')) +}; + +console.log(TAG, `platform : ${process.platform}`); + +const httpPort = process.env.HTTP_PORT || 9559; +const httpsPort = process.env.PORT || process.env.HTTPS_PORT || 5443; +const httpsServer = require('https').createServer(options, app); +const httpServer = require('http').createServer(app); +const io = require('socket.io')(); +const isTizen = process.platform === 'tizen'; +const supportMessagePort = isTizen; +// Implementation for edge orchestration +const supportEdgeOrchestration = + typeof webapis !== 'undefined' && Object.hasOwnProperty.call(webapis, 'edge'); +console.log(TAG, `supportEdgeOrchestration : ${supportEdgeOrchestration}`); + +io.attach(httpServer); +io.attach(httpsServer); + +const clients = new Set(); +const workers = new Map(); +const sockets = new Map(); +let edgeForCastanets = null; +let forceQuitTimer = null; +let isMeerkatStarted = false; +let statusIntervalId = null; +let localPort = null; + +app.set('host', '0.0.0.0'); + +if (isTizen) { + app.use(express.static(path.join(__dirname, './public'))); + app.use(express.static(path.join(__dirname, '../../shared/res'))); +} else { + app.use( + '/offload.html', + express.static(path.join(__dirname, '../../offload/apps/web/offload.html')) + ); + // Host offload-worker + app.use( + '/offload-worker', + express.static(path.join(__dirname, '../../offload-worker/apps/web/')) + ); + app.use( + '/offload-worker/offload-worker.js', + express.static(path.join(__dirname, '../../dist/offload-worker.js')) + ); + app.use( + '/face.webm', + express.static(path.join(__dirname, '../../offload-worker/apps/web/face.webm')) + ); + const serveIndex = require('serve-index'); + app.use( + '/', + express.static(path.join(__dirname, '../../dist')), + express.static(path.join(__dirname, '../../offload/sample')), + serveIndex(path.join(__dirname, '../../offload/sample')) + ); + app.use( + '/test', + express.static(path.join(__dirname, '../../test')), + serveIndex(path.join(__dirname, '../../test')) + ); +} + +function onConnection(socket) { + if (isTizen && !isMeerkatStarted) { + try { + console.log(TAG, 'Try to start Meerkat client.'); + tizen.application.launch( + 'org.tizen.meerkat.client', + () => { + console.log(TAG, 'Meerkat client is started.'); + isMeerkatStarted = true; + }, + err => console.error(TAG, 'Failed to launch Meerkat client. ' + err) + ); + } catch (err) { + console.error(TAG, 'Failed to launch Meerkat client. ' + err); + } + } + console.log(TAG, `connection from '${socket.id}.`); + sockets.set(socket.id, socket); + + // client creates a session. + socket.on('create', async function () { + if (forceQuitTimer !== null) { + clearTimeout(forceQuitTimer); + } + + if (clients.has(socket.id)) { + console.log(TAG, `already created by ${socket.id}.`); + return; + } + clients.add(socket.id); + + let qr = null; + const myAddress = getMyAddress(); + if (myAddress) { + try { + qr = await QRCode.toDataURL( + 'https://' + myAddress + ':5443/offload-worker.html' + ); + } catch (err) { + console.error(TAG, 'unabled to generate QR: ' + err); + } + } + + socket.emit('greeting', { + qrCode: qr, + workers: Array.from(workers) + }); + + console.log( + TAG, + `[client] session created by ${socket.id}. workers.size : ${workers.size}` + ); + + if (supportEdgeOrchestration) { + socket.emit( + 'capabilities', + Array.from(edgeForCastanets.getCapabilities()) + ); + } + }); + + socket.on('getcapabilities', function () { + console.log(TAG, 'getcapabilities'); + if (supportEdgeOrchestration) { + socket.emit( + 'capabilities', + Array.from(edgeForCastanets.getCapabilities()) + ); + } else { + socket.emit('capabilities', []); + } + }); + + socket.on('requestService', function (workerId) { + if (supportEdgeOrchestration) { + edgeForCastanets.requestService(workerId); + } + }); + + // new worker has been joined. + socket.on('join', function (worker) { + if (supportEdgeOrchestration) { + let deviceIp = socket.request.connection.remoteAddress; + if (deviceIp.indexOf('::ffff:') !== -1) { + deviceIp = deviceIp.substr(7, deviceIp.length); + } + + if (deviceIp) { + edgeForCastanets.joinDevice(deviceIp); + } + } + + workers.set(worker.id, { + socketId: socket.id, + name: worker.name, + features: worker.features, + mediaDeviceInfos: worker.mediaDeviceInfos, + compute_tasks: 0 + }); + console.log( + TAG, + `worker[${workers.size}] join: '${worker.id}' - '${socket.id}', '${worker.name}'`, + worker.features + ); + + for (const client of clients) { + const clientSocket = sockets.get(client); + clientSocket.emit('worker', { + event: 'join', + workerId: worker.id, + socketId: socket.id, + name: worker.name, + features: worker.features, + mediaDeviceInfos: worker.mediaDeviceInfos + }); + } + }); + + // route message between clients. + socket.on('message', function (data) { + console.log(TAG, `message ${JSON.stringify(data)}`); + let socketId = null; + if (workers.has(data.to)) { + socketId = workers.get(data.to).socketId; + } else if (clients.has(data.to)) { + socketId = data.to; + } + + if (socketId) { + const socket = sockets.get(socketId); + socket.emit('message', data); + } + }); + + socket.on('disconnect', function (reason) { + const socketId = socket.id; + sockets.delete(socketId); + if (clients.has(socketId)) { + console.log(TAG, `[client] session terminated by client: ${socketId}`); + clients.delete(socketId); + + // broadcast to offload-worker + for (const socket of sockets.values()) { + socket.emit('client', { + event: 'bye', + socketId: socketId + }); + } + + if (clients.size === 0) { + if (supportMessagePort) { + closeServer(); + } + forceQuitTimer = setTimeout(function () { + console.log( + TAG, + 'All clients are destroyed. Broadcast \'forceQuit\' to workers' + ); + for (const socket of sockets.values()) { + socket.emit('client', { + event: 'forceQuit', + socketId: socketId + }); + } + }, 5000); + } + } else { + if (supportEdgeOrchestration) { + let deviceIp = socket.request.connection.remoteAddress; + if (deviceIp.indexOf('::ffff:') !== -1) { + deviceIp = deviceIp.substr(7, deviceIp.length); + } + + if (deviceIp) { + edgeForCastanets.disconnectDevice(deviceIp); + } + } + + let workerId = null; + workers.forEach(function (value, key, map) { + if (value.socketId === socket.id) { + workerId = key; + } + }); + + if (workerId) { + for (const client of clients) { + const socket = sockets.get(client); + socket.emit('worker', { + event: 'bye', + workerId: workerId, + socketId: socket.id + }); + } + workers.delete(workerId); + console.log(TAG, `worker[${workers.size}] bye: '${workerId}'`); + } + } + }); +} + +io.of('/offload-js').on('connection', onConnection); + +if (supportEdgeOrchestration) { + edgeForCastanets = new Edge( + 'castanets', + 'android', + 'com.samsung.android.castanets' + ); +} + +function startServer() { + console.log(TAG, 'starting server...'); + + if (!httpsServer.listening) { + httpsServer.listen(httpsPort, function () { + console.log(TAG, `server is listening on https ${httpsPort} port.`); + }); + } + + if (!httpServer.listening) { + httpServer.listen(httpPort, function () { + console.log(TAG, `server is listening on http ${httpPort} port.`); + }); + } +} + +function closeServer() { + console.log(TAG, 'closing server...'); + + if (httpsServer.listening) { + httpsServer.close(err => { + if (err) { + console.error('failed to close the https server:', err); + } + }); + } + + if (httpServer.listening) { + httpServer.close(err => { + if (err) { + console.error('failed to close the http server:', err); + } + }); + } +} + +// Check the client status +function checkConnectionStatus() { + for (const client of clients) { + const socket = sockets.get(client); + if (socket.constructor === SocketTizen) { + try { + socket.emit('status'); + } catch (e) { + console.error(TAG, `Failed to check ${client} status`); + socket.handleEvents('disconnect'); + } + } + } +} + +function handleMessagePort(messages) { + if (messages.length === 0) { + console.error(TAG, 'Not found message'); + return; + } + + const message = messages[0]; + const event = message.key; + const value = JSON.parse(message.value); + const id = value.id; + + if (event === 'connect') { + // FIXME: The message port does not guarantee that the connection has + // been disconnected when the page is reloaded. Therefore, if a new + // connection occurs with the same id, the existing connection is + // disconnected. + if (sockets.has(id)) { + console.log(TAG, `Disconnect already connected socket: ${id}`); + const socket = sockets.get(id); + socket.handleEvents('disconnect'); + } + + const socket = new SocketTizen(id, localPort); + socket.on('connection', onConnection); + socket.connect(); + sockets.set(id, socket); + startServer(); + } else { + const socket = sockets.get(id); + socket.handleEvents(event, value.data); + } +} + +module.exports.onStart = () => { + console.log(`${TAG} onStart is called`); + if (supportMessagePort) { + console.log(TAG, 'listening tizen messageport...'); + localPort = tizen.messageport.requestLocalMessagePort('offload'); + localPort.addMessagePortListener(handleMessagePort); + + // Prevent to terminate the process + statusIntervalId = setInterval(checkConnectionStatus, 1000); + } else { + startServer(); + } +}; + +module.exports.onStop = () => { + console.log(`${TAG} onStop is called`); + if (supportMessagePort) { + for (const socket of sockets.values()) { + socket.close(); + } + if (localPort !== null) { + localPort.removeMessagePortListener(handleMessagePort); + } + if (statusIntervalId !== null) { + clearInterval(statusIntervalId); + } + } + + closeServer(); +}; + +module.exports.onRequest = () => { + console.log(`${TAG} onRequest is called`); +}; diff --git a/device_home/signaling_server/gen/socket-tizen.js b/device_home/signaling_server/gen/socket-tizen.js index bb1f726..a94ede6 100644 --- a/device_home/signaling_server/gen/socket-tizen.js +++ b/device_home/signaling_server/gen/socket-tizen.js @@ -58,7 +58,7 @@ class SocketTizen { this.handleEvents('connection', this); this.emit('connect'); } catch (error) { - console.error(TAG, `Messageport connection failed: ` + error); + console.error(TAG, 'Messageport connection failed: ' + error); } } diff --git a/device_home/signaling_server/gen/util.js b/device_home/signaling_server/gen/util.js index 38a0e8e..4c79773 100644 --- a/device_home/signaling_server/gen/util.js +++ b/device_home/signaling_server/gen/util.js @@ -4,9 +4,9 @@ function getMyAddress() { const interfaces = os.networkInterfaces(); const addresses = {}; for (const intf in interfaces) { - if (interfaces.hasOwnProperty(intf)) { + if (Object.hasOwnProperty.call(interfaces, intf)) { for (const addr in interfaces[intf]) { - if (interfaces[intf].hasOwnProperty(addr)) { + if (Object.hasOwnProperty.call(interfaces[intf], addr)) { const address = interfaces[intf][addr]; if (address.family === 'IPv4' && !address.internal) { addresses[intf] = address.address; diff --git a/packaging/config.xml.in b/packaging/config.xml.in index e2a99de..9485b1d 100644 --- a/packaging/config.xml.in +++ b/packaging/config.xml.in @@ -18,4 +18,9 @@ DeviceHomeService DeviceHomeService + + + SignalingServer + SignalingServer + diff --git a/packaging/wrtjs.spec b/packaging/wrtjs.spec index e605fba..1e27eb1 100644 --- a/packaging/wrtjs.spec +++ b/packaging/wrtjs.spec @@ -26,6 +26,7 @@ Source: %{name}-%{version}.tar.gz %define _use_nmt 0 %endif %define _use_category 0 + %define _use_d2d_offload 1 %endif BuildRequires: pkgconfig(chromium-efl) -- 2.7.4 From a31ba581183ca1ee6bd8143cfb5a517d43f59dfd Mon Sep 17 00:00:00 2001 From: DongHyun Song Date: Thu, 21 Apr 2022 20:21:51 +0900 Subject: [PATCH 13/16] [DeviceHome] Refactors EventSource handler Refactors EventSource handlers for multiple purpose. - updateapp-list: update app list when install/uninstall - redirect-url: request to open the URL - remote-message: 'remotemessage' CustomEvent - ime-event: handle for remote input Change-Id: I637bcdc17ed8fd3ed7b69b93d50a5c431a385d35 Signed-off-by: DongHyun Song --- device_home/client/js/myApps.js | 29 +++++++++++++++++++++++++---- device_home/service/service.js | 39 +++++++++++++++++++++++++++++++++------ 2 files changed, 58 insertions(+), 10 deletions(-) mode change 100755 => 100644 device_home/service/service.js diff --git a/device_home/client/js/myApps.js b/device_home/client/js/myApps.js index 869db93..8eebe74 100644 --- a/device_home/client/js/myApps.js +++ b/device_home/client/js/myApps.js @@ -176,11 +176,28 @@ const myappsmodule = {}; }) } - function init() { - var eventSource = new EventSource(serverURL + ':' + serverPort + '/updateAppList'); + function initEventSource() { + const eventSource = new EventSource(serverURL + ':' + serverPort + '/listenMessage'); eventSource.addEventListener('message', evt => { - showListView(JSON.parse(evt.data)); - UpdateWebClip(JSON.parse(evt.data)); + console.log(`evt.data : ${evt.data}`); + const message = JSON.parse(evt.data); + switch (message.type) { + case 'updateapp-list': + showListView(message.body); + UpdateWebClip(message.body); + break; + case 'redirect-url': + const body = JSON.parse(message.body); + window.open(body.url, '_blank'); + break; + case 'remote-message': + document.dispatchEvent(new CustomEvent('remotemessage', message.body)); + break; + case 'ime-event': + break; + default: + console.log(`no matched case : ${message.type}`); + } }, false); eventSource.addEventListener('open', evt => { console.log("Connected to..."); @@ -192,6 +209,10 @@ const myappsmodule = {}; console.log('Connecting to...'); } }, false); + } + + function init() { + initEventSource(); showList(); } window.onload = init; diff --git a/device_home/service/service.js b/device_home/service/service.js old mode 100755 new mode 100644 index 1bedb6f..d73de5a --- a/device_home/service/service.js +++ b/device_home/service/service.js @@ -19,7 +19,8 @@ const sessionMiddleware = session({ cookie: { httpOnly: true, secure: false, -}}); + } +}); const PUBLIC_DOMAIN = 'https://devicehome.net'; const TAG = '[DeviceHome][service.js]' @@ -213,10 +214,10 @@ function setPackageInfoEventListener() { }); console.log(`${TAG} Emit app list`); // for both companion and webclip - evtEmit.emit('updateapplist', 'message', dataApps); + evtEmit.emit('updateapp-list', 'message', dataApps); relayServer(httpserver, dataApps, sessionMiddleware, clientPublicKeys, packageInfo.id); } else { - evtEmit.emit('updateapplist', 'message', dataApps); + evtEmit.emit('updateapp-list', 'message', dataApps); } }, onupdated: function(packageInfo) { @@ -233,12 +234,24 @@ function setPackageInfoEventListener() { } console.log(`${TAG} The package ${packageId} is uninstalled`); removeD2Ddata(packageId); - evtEmit.emit('updateapplist', 'message', dataApps); + evtEmit.emit('updateapp-list', 'message', dataApps); } }; tizen.package.setPackageInfoEventListener(packageEventCallback); } +function registerWrtMessagePort() { + let localPort = tizen.messageport.requestLocalMessagePort('wrt.message.port'); + localPort.addMessagePortListener((data, remotePort) => { + try { + evtEmit.emit(data[0]['key'], 'message', data[0]['value']); + } catch (e) { + console.log('wrt.message.port has exception' + e); + } + }); +} +} + function unsetPackageInfoEventListener() { tizen.package.unsetPackageInfoEventListener(); } @@ -485,11 +498,25 @@ var HTTPserverStart = function() { 'Cache-Control': 'no-cache', 'Connection': 'keep-alive' }); - evtEmit.on('updateapplist', (event, data) => { + evtEmit.on('updateapp-list', (event, data) => { res.write('event: ' + String(event) + '\n' + 'data: ' + JSON.stringify(data) + '\n\n'); }); }); + app.get('/listenMessage', (req, res) => { + res.writeHead(200, { + 'Content-Type': 'text/event-stream', + 'Cache-Control': 'no-cache', + 'Connection': 'keep-alive' + }); + const eventTypes = ['updateapp-list', 'redirect-url', 'remote-message', 'ime-event']; + for (const type of eventTypes) { + evtEmit.on(type, (event, data) => { + res.write(`event: ${event}\ndata: { "type": "${type}", "body": ${JSON.stringify(data)}}\n\n`); + }); + } + }); + app.get('/pincode/publicKey', async (req, res) => { tryCount = 0; await displayPincode(req); @@ -581,7 +608,7 @@ module.exports.onStop = function() { console.log(`${TAG} Server Terminated`); } unsetPackageInfoEventListener(); - evtEmit.off('updateapplist'); + evtEmit.off('updateapp-list'); console.log(`${TAG} onStop is called in DNS Resolver`); }; -- 2.7.4 From c7c5c4f65d06a8f78e4ca6c249b1b3b92312f7a5 Mon Sep 17 00:00:00 2001 From: DongHyun Song Date: Thu, 28 Apr 2022 13:49:53 +0900 Subject: [PATCH 14/16] [Service] Support webapis.getAppIdsByMetadata() This new API will be used to find proper apps quickly more than tizen.application.getAppsInfo() Related patch: https://review.tizen.org/gerrit/#/c/platform/framework/web/chromium-efl/+/274342/ Change-Id: Ibe8c2a279943cc85570f057e38721aa5d4b95729 Signed-off-by: DongHyun Song --- wrt_app/service/device_api_router.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/wrt_app/service/device_api_router.ts b/wrt_app/service/device_api_router.ts index 40c33d7..cde4134 100644 --- a/wrt_app/service/device_api_router.ts +++ b/wrt_app/service/device_api_router.ts @@ -73,7 +73,11 @@ export class DeviceAPIRouter { else if (wrt.da) return 'DA'; else return 'common'; } + global.webapis.getAppIdsByMetadata = (metadata: string) => { + return wrt.getAppIdsByMetadata(metadata); + } Object.defineProperties(global.webapis, { + getAppIdsByMetadata: { writable: false, enumerable: true }, getCallerAppId: { writable: false, enumerable: true }, getPackageId: { writable: false, enumerable: true }, getServiceId: { writable: false, enumerable: true }, -- 2.7.4 From eb94d74c1e4cd899cbfd0d625e64e12d6f76689c Mon Sep 17 00:00:00 2001 From: Hunseop Jeong Date: Thu, 28 Apr 2022 17:15:41 +0900 Subject: [PATCH 15/16] [SignalingServer] Move the offload.js to the shared space Move the offload.js to the shared/res/ to access the offload.js from the xwalk extension. Change-Id: If4b802c99e40de23a198cee173c9f5969a371599 Signed-off-by: Hunseop Jeong --- device_home/{signaling_server/gen/public => shared/res}/offload.js | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename device_home/{signaling_server/gen/public => shared/res}/offload.js (100%) diff --git a/device_home/signaling_server/gen/public/offload.js b/device_home/shared/res/offload.js similarity index 100% rename from device_home/signaling_server/gen/public/offload.js rename to device_home/shared/res/offload.js -- 2.7.4 From 8a7439e26f9cd37f4d171da9e174ae87fe721b95 Mon Sep 17 00:00:00 2001 From: liwei Date: Thu, 12 May 2022 13:41:21 +0800 Subject: [PATCH 16/16] [Service] Skip start service when app terminate & Check wrt-service launcher to 4s Now an issue happen, service app is launched for a little time, then wrt-service-launcher is killed by unknown reason, then wrt-service start to stop, guess xwalk-extension is not stopped completely, at the same time, service app is reqeusting preview, then crash happen, so do below changes. 1. Skip start service when app is terminating. 2. Check wrt-service-launcher from 2s to 4s. Change-Id: I4a7c4b946a89d1a1f6a46903e3582a1d91248888 Signed-off-by: liwei --- wrt_app/service/service_manager.ts | 13 +++++++++++++ wrt_app/service/service_runner.ts | 4 ++-- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/wrt_app/service/service_manager.ts b/wrt_app/service/service_manager.ts index c639eba..de807eb 100644 --- a/wrt_app/service/service_manager.ts +++ b/wrt_app/service/service_manager.ts @@ -24,6 +24,15 @@ function checkDyingWorker() { } } +function IsDyingWorker(id: string) { + let dyingWorkers = Object.keys(dyingWorkerQueue); + if (dyingWorkers.length && dyingWorkerQueue[id]) { + return ((dyingWorkerQueue[id] === 'will-terminate') || + (dyingWorkerQueue[id] === 'terminated')) + } + return false; +} + function createWorker(id: string, startService: string, filename: string) { if (workers[id]) { workers[id].postMessage({ type: 'wake' }); @@ -73,6 +82,10 @@ let initializeExtensionOnMain = () => { } export function startService(id: string, filename: string) { + if(IsDyingWorker(id)) { + console.debug(`startService - ${id} is in stop status, skip start`); + return; + } console.debug(`startService - ${id}`); initializeExtensionOnMain(); if (global['serviceType'] === 'STANDALONE') { diff --git a/wrt_app/service/service_runner.ts b/wrt_app/service/service_runner.ts index 405c9e9..5a1b65c 100644 --- a/wrt_app/service/service_runner.ts +++ b/wrt_app/service/service_runner.ts @@ -49,12 +49,12 @@ let requestStopService = (id: string) => { let app: any = null; let dummyTimer: any; -let periodLauncherAlive = 20; // 2s +let periodLauncherAlive = 40; // 4s let checkLauncherAlive = (id: string) => { periodLauncherAlive--; if (!periodLauncherAlive) { - periodLauncherAlive = 20; + periodLauncherAlive = 40; if (!wrt.checkLauncherAlive(id)) { console.debug(`${id} launcher was killed.`) requestStopService(id); -- 2.7.4