3 const express = require('express');
4 const http = require('http');
5 const path = require("path");
6 const relayServer = require('./relay-server.js');
7 const session = require('express-session');
8 const EventEmitter = require('events');
9 const XMLHttpRequest = require("xmlhttprequest").XMLHttpRequest;
10 const crypto = require('crypto');
11 const { Security } = require('./security.js');
13 const PUBLIC_DOMAIN = 'http://219.254.222.198';
14 const TAG = '[DeviceHome][service.js]'
15 const TIZEN_WEB_APP_SHARED_RESOURCES = 'shared/res/';
16 const WEBCLIP_DIRECTORY = 'webclip';
17 const WEBCLIP_MANIFEST = 'manifest.json';
18 const is_tv = webapis.cachedProperty !== undefined;
24 const security = new Security();
28 var clientRouter = express.Router();
29 var httpserver, evtEmit;
30 var platform_app_path = '/opt/usr/globalapps';
34 var JSEncryptLib = require('./jsencrypt');
40 // Watch together doesn't use pincode just for demo.
43 function addD2Ddata(appPkgID, appAppID, appName, iconPath) {
46 let metaDataArray = tizen.application.getAppMetaData(appAppID);
47 metaDataArray = metaDataArray.filter(function(metaData) {
48 if (metaData.key !== "d2dservice")
51 if (metaData.value !== "enable")
52 metaAppID = metaData.value;
55 metaDataArray.forEach(function() {
56 const appPath = path.join(platform_app_path, appPkgID, TIZEN_WEB_APP_SHARED_RESOURCES);
59 appAppID: metaAppID === '' ? appAppID : metaAppID,
63 app.path = path.join(appPath);
64 console.log(`${TAG} app : ${JSON.stringify(app)}`);
70 function removeD2Ddata(packageId) {
71 for (var j = 0; j < dataApps.length; j++) {
72 if (packageId && !packageId.indexOf(dataApps[j].d2dApp.appPkgID)) {
73 dataApps.splice(j, 1);
79 for (let i = 0; i < apps.length; i++) {
80 addD2Ddata(apps[i].packageId, apps[i].id, apps[i].name, apps[i].iconPath);
84 function getAppList() {
85 if (tizen.application) {
87 tizen.application.getAppsInfo(function(applications) {
90 getWebclipsManifest();
100 function getWebclipsManifestByApp(app) {
101 let fileHandle = undefined;
103 const filePath = path.join(app.path, WEBCLIP_DIRECTORY, WEBCLIP_MANIFEST);
105 console.log(`${TAG} webclip path : ${filePath}`);
107 fileHandle = tizen.filesystem.openFile(filePath, "r");
109 console.log(`${TAG} tizen.filesystem.openFile (error): ${filePath} ${err}`);
114 data = fileHandle.readString();
115 data = data.replace(/\n/g, "");
116 data = JSON.parse(data);
118 app.webclip.manifest = data;
120 console.log(`${TAG} fileHandle.readString (error): ${err}`);
127 function getWebclipsManifest() {
129 getWebclipsManifestByApp
133 function setPackageInfoEventListener() {
134 const packageEventCallback = {
135 oninstalled: function(packageInfo) {
136 console.log(`${TAG} The package ${packageInfo.name} is installed`);
137 const app = addD2Ddata(packageInfo.id, packageInfo.appIds[0], packageInfo.name, packageInfo.iconPath);
139 getWebclipsManifestByApp(app);
140 evtEmit.emit("updateapplist", "message", dataApps);
142 onupdated: function(packageInfo) {
143 console.log(`${TAG} The package ${packageInfo.name} is updated`);
145 onuninstalled: function(packageId) {
146 console.log(`${TAG} The package ${packageId} is uninstalled`);
147 removeD2Ddata(packageId);
148 evtEmit.emit("updateapplist", "message", dataApps);
151 tizen.package.setPackageInfoEventListener(packageEventCallback);
154 function unsetPackageInfoEventListener() {
155 tizen.package.unsetPackageInfoEventListener();
158 function getWebClipsList() {
162 dataApps.forEach(function(app) {
164 if (app.webclip && app.webclip.manifest) {
166 url: path.join('client', 'webclip', app.webclip.manifest.name),
171 appID: app.d2dApp.appAppID,
172 pkgID: app.d2dApp.appPkgID,
175 webClipsList: webclips
182 function sendLoginIdAndDeviceName(login_id, device_ip) {
183 const device_name = webapis.mde.getDeviceName();
184 console.log(`${TAG} login_id = ${login_id}, device_name = ${device_name}`);
186 const xhr = new XMLHttpRequest();
189 device_name: device_name,
192 xhr.onreadystatechange = function() {
193 if (xhr.readyState === xhr.DONE) {
194 console.log(`${TAG} xhr text: ${xhr.responseText}`);
195 if (xhr.status === 200 || xhr.status === 201) {
196 if (xhr.responseText === 'DEVICE_EXISTS') {
197 console.log(`${TAG} device exists`);
200 console.log(`${TAG} xhr error: ${xhr.status}`);
204 xhr.open('POST', PUBLIC_DOMAIN + '/registerDevice');
205 xhr.setRequestHeader('Content-Type', 'application/json');
206 xhr.send(JSON.stringify(keyVal));
209 function updateDNSresolver(device_ip) {
210 console.log(`${TAG} Server is listening on ${device_ip}:${g.port}`);
211 let login_id = 'stester81@gmail.com';
213 login_id = webapis.mde.getCurrentLoginId();
214 sendLoginIdAndDeviceName(login_id, device_ip);
217 function comparePincode(req, res, encrypted) {
218 console.log(`${TAG} comparePincode`);
219 console.log(`${TAG} encrypted : ${encrypted}`);
220 // Decrypt pincode using private key
221 const decrypt = new JSEncryptLib.JSEncrypt();
222 decrypt.setPrivateKey(security.getPrivateKey());
223 const decrypted = decrypt.decrypt(encrypted);
224 console.log(`${TAG} decrypted : ${decrypted}`);
226 const pincode_passed = decrypted === pincode ? true : false;
227 console.log(`${TAG} pincode result : ${pincode_passed}`);
228 if (pincode_passed) {
229 req.session.pincode = pincode;
230 console.log(`${TAG} session : ${JSON.stringify(req.session)}`);
232 res.send(pincode_passed);
235 async function displayPincode() {
237 const byteData = crypto.randomBytes(256);
238 pincode = parseInt(byteData.toString('hex').substr(0, 8), 16).toString().substr(0, 4);
240 await security.awaitKeyPair();
241 // Show pincode popup
242 webapis.postPlainNotification("Input Pincode: ", pincode, 10);
245 var HTTPserverStart = function() {
246 evtEmit = new EventEmitter();
247 const app = express();
248 app.engine('html', require('ejs').renderFile);
249 app.set('view engine', 'ejs');
250 app.set('views', `${g.baseDir}/../`);
251 app.use('/pincode', express.static(`${g.baseDir}/../pincode`));
254 app.use(express.urlencoded({
257 app.use(express.json());
258 // For session management
261 saveUninitialized: true,
262 secret: 'mde framework',
269 var sessionChecker = function(req, res, next) {
270 console.log(`${TAG} url : ${req.url}`);
271 console.log(`${TAG} session : ${JSON.stringify(req.session)}`);
272 const rawIp = req.socket.remoteAddress;
273 const ip = rawIp.slice(rawIp.lastIndexOf(":") + 1);
274 // The pincode page and local connections are allowed without session.
275 if (req.session.pincode === undefined &&
276 !req.url.includes('id=') &&
277 !req.url.includes('/pincode/') &&
278 !non_ip_list.includes(ip)) {
279 console.log(`${TAG} Not valid access`);
280 res.redirect(401, PUBLIC_DOMAIN);
287 app.use(sessionChecker);
289 const appProxy = require('./app_proxy');
290 app.use('/app', appProxy(app, g.port));
291 app.use('/client', clientRouter);
292 console.log(`${TAG} __dirname: ${__dirname}`);
295 platform_app_path = '/opt/usr/apps'
296 console.log(`${TAG} TV Profile`);
299 var tizenApp = tizen.application.getCurrentApplication();
300 console.log(`${TAG} ID, packageId: ${tizenApp.appInfo.id} ${tizenApp.appInfo.packageId}`);
301 serverAppId = tizenApp.appInfo.id.split('.')[0];
302 g.baseDir = __dirname.split(serverAppId)[0];
303 console.log(`${TAG} g.baseDir: ${g.baseDir}`);
305 clientRouter.get('/webclip/*', function(req, res) {
306 let file = req.originalUrl.replace('/client/webclip/', '').replace(/\?.+$/, '');
307 let webclipName = '';
309 const match = file.match(/^[^\/]+/);
311 webclipName = match[0];
313 console.log(`${TAG} webclip name: ${webclipName}`);
315 // find appId by webclip name
316 const app = dataApps.filter(function(app) {
317 return !!app.webclip && app.webclip.manifest.name === webclipName;
320 appId = app.d2dApp.appPkgID;;
323 console.log(`${TAG} root : ${platform_app_path}/${appId}/${TIZEN_WEB_APP_SHARED_RESOURCES}/${WEBCLIP_DIRECTORY}`);
325 root: path.join(platform_app_path, appId, TIZEN_WEB_APP_SHARED_RESOURCES, WEBCLIP_DIRECTORY)
328 // remove weblip name from path
329 file = file.replace(webclipName + '/', '');
330 res.sendFile(file, options, function(err) {
332 console.log(`${TAG} err: ${err}`);
333 res.send("err", err);
335 console.log(`${TAG} res.sendFile() done: ${file}`);
340 clientRouter.get('/updateWebclip', function(req, res) {
341 console.log(`${TAG} get(/updateWebclip)`);
342 const apps = getWebClipsList();
349 console.log(`${TAG} webclip : ${JSON.stringify(result)}`);
353 clientRouter.get('/*', function(req, res) {
354 const file = req.originalUrl.replace('/client/', '').replace(/\?.+$/, '');
355 const pkgId = webapis.getPackageId();
356 const fullPath = require('path').join(g.baseDir, pkgId, '/res/wgt/client', file);
357 console.log(`${TAG} pkgId: ${pkgId}, fullPath: ${fullPath}`);
358 res.sendFile(fullPath);
361 app.get('/', function(req, res) {
362 console.log(`${TAG} URL parameter : ${req.originalUrl}`);
363 urlParam = req.originalUrl;
366 res.redirect("/pincode/pincode.html");
368 if (req.query.roomId !== undefined) {
369 // FIXME: Remove app logic here
370 res.render("client/invited.html");
373 res.render("client/client.html");
378 app.get('/d2dIcon/*', (req, res) => {
379 let fullPath = req.originalUrl.replace("d2dIcon", platform_app_path);
380 res.sendFile(fullPath);
383 app.get('/appList', (req, res) => {
387 app.get('/updateAppList', (req, res) => {
389 'Content-Type': 'text/event-stream',
390 'Cache-Control': 'no-cache',
391 'Connection': 'keep-alive'
393 evtEmit.on("updateapplist", (event, data) => {
394 res.write("event: " + String(event) + "\n" + "data: " + JSON.stringify(data) + "\n\n");
398 app.get('/pincode/publicKey', async (req, res) => {
399 await displayPincode();
400 const key = security.getPublicKey();
401 console.log(`${TAG} Send public key`);
405 app.post('/pincode/pinCodeToServer', express.json(), (req, res) => {
406 // Verify encrypted pincode
407 const resultData = req.body['pincode'];
408 console.log(`${TAG} pinCodeToServer resultData : ${resultData}`);
409 comparePincode(req, res, resultData);
412 app.post('/d2d', (req, res) => {
413 if (req.session.pincode !== undefined) {
414 console.log(`${TAG} client.html`);
415 res.render("client/client.html");
417 console.log(`${TAG} no client.html`);
418 res.redirect(401, PUBLIC_DOMAIN);
422 // receive data or cmd to app on device
423 app.post('/app', (req, res) => {
429 app.get('/service', (req, res) => {
432 app.post('/url', (req, res) => {
433 webapis.mde.launchBrowserFromUrl(req.body.url);
436 httpserver = http.createServer(app);
437 httpserver.listen(g.port, function() {
438 const interfaces = require('os').networkInterfaces();
439 for (const devName in interfaces) {
440 if (interfaces.hasOwnProperty(devName)) {
441 const iface = interfaces[devName];
442 for (let i = 0; i < iface.length; i++) {
443 const alias = iface[i];
444 if (alias.family === 'IPv4' && !non_ip_list.includes(alias.address) && !alias.internal)
445 updateDNSresolver(alias.address);
450 relayServer(httpserver);
453 module.exports.getUrlParam = function() {
454 console.log(`${TAG} getUrlParam is called`);
458 module.exports.onStart = function() {
461 setPackageInfoEventListener();
462 console.log(`${TAG} onStart is called in DNS Resolver`);
465 module.exports.onStop = function() {
468 console.log(`${TAG} Server Terminated`);
470 unsetPackageInfoEventListener();
471 evtEmit.off("updateapplist");
472 console.log(`${TAG} onStop is called in DNS Resolver`);