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');
42 // Watch together doesn't use pincode just for demo.
45 function addD2Ddata(appPkgID, appAppID, appName, iconPath) {
48 let metaDataArray = tizen.application.getAppMetaData(appAppID);
49 metaDataArray = metaDataArray.filter(function(metaData) {
50 if (metaData.key !== "d2dservice")
53 if (metaData.value !== "enable")
54 metaAppID = metaData.value;
57 metaDataArray.forEach(function() {
58 const appPath = path.join(platform_app_path, appPkgID, TIZEN_WEB_APP_SHARED_RESOURCES);
61 appAppID: metaAppID === '' ? appAppID : metaAppID,
65 app.path = path.join(appPath);
66 console.log(`${TAG} app : ${JSON.stringify(app)}`);
72 function removeD2Ddata(packageId) {
73 for (var j = 0; j < dataApps.length; j++) {
74 if (packageId && !packageId.indexOf(dataApps[j].d2dApp.appPkgID)) {
75 dataApps.splice(j, 1);
81 for (let i = 0; i < apps.length; i++) {
82 addD2Ddata(apps[i].packageId, apps[i].id, apps[i].name, apps[i].iconPath);
86 function getAppList() {
87 if (tizen.application) {
89 tizen.application.getAppsInfo(function(applications) {
92 getWebclipsManifest();
102 function getWebclipsManifestByApp(app) {
103 let fileHandle = undefined;
105 const filePath = path.join(app.path, WEBCLIP_DIRECTORY, WEBCLIP_MANIFEST);
107 console.log(`${TAG} webclip path : ${filePath}`);
109 fileHandle = tizen.filesystem.openFile(filePath, "r");
111 console.log(`${TAG} tizen.filesystem.openFile (error): ${filePath} ${err}`);
116 data = fileHandle.readString();
117 data = data.replace(/\n/g, "");
118 data = JSON.parse(data);
120 app.webclip.manifest = data;
122 console.log(`${TAG} fileHandle.readString (error): ${err}`);
129 function getWebclipsManifest() {
131 getWebclipsManifestByApp
135 function setPackageInfoEventListener() {
136 const packageEventCallback = {
137 oninstalled: function(packageInfo) {
138 console.log(`${TAG} The package ${packageInfo.name} is installed`);
139 const app = addD2Ddata(packageInfo.id, packageInfo.appIds[0], packageInfo.name, packageInfo.iconPath);
141 getWebclipsManifestByApp(app);
142 evtEmit.emit("updateapplist", "message", dataApps);
144 onupdated: function(packageInfo) {
145 console.log(`${TAG} The package ${packageInfo.name} is updated`);
147 onuninstalled: function(packageId) {
148 console.log(`${TAG} The package ${packageId} is uninstalled`);
149 removeD2Ddata(packageId);
150 evtEmit.emit("updateapplist", "message", dataApps);
153 tizen.package.setPackageInfoEventListener(packageEventCallback);
156 function unsetPackageInfoEventListener() {
157 tizen.package.unsetPackageInfoEventListener();
160 function getWebClipsList() {
164 dataApps.forEach(function(app) {
166 if (app.webclip && app.webclip.manifest) {
168 url: path.join('client', 'webclip', app.webclip.manifest.name),
173 appID: app.d2dApp.appAppID,
174 pkgID: app.d2dApp.appPkgID,
177 webClipsList: webclips
184 function sendLoginIdAndDeviceName(login_id, device_ip) {
185 const device_name = webapis.mde.getDeviceName();
186 console.log(`${TAG} login_id = ${login_id}, device_name = ${device_name}`);
188 const xhr = new XMLHttpRequest();
191 device_name: device_name,
194 xhr.onreadystatechange = function() {
195 if (xhr.readyState === xhr.DONE) {
196 console.log(`${TAG} xhr text: ${xhr.responseText}`);
197 if (xhr.status === 200 || xhr.status === 201) {
198 if (xhr.responseText === 'DEVICE_EXISTS') {
199 console.log(`${TAG} device exists`);
202 console.log(`${TAG} xhr error: ${xhr.status}`);
206 xhr.open('POST', PUBLIC_DOMAIN + '/registerDevice');
207 xhr.setRequestHeader('Content-Type', 'application/json');
208 xhr.send(JSON.stringify(keyVal));
211 function updateDNSresolver(device_ip) {
212 console.log(`${TAG} Server is listening on ${device_ip}:${g.port}`);
213 let login_id = 'stester81@gmail.com';
215 login_id = webapis.mde.getCurrentLoginId();
216 sendLoginIdAndDeviceName(login_id, device_ip);
219 function comparePincode(req, res, encrypted) {
220 console.log(`${TAG} comparePincode`);
221 console.log(`${TAG} encrypted : ${encrypted}`);
222 // Decrypt pincode using private key
223 const decrypt = new JSEncryptLib.JSEncrypt();
224 decrypt.setPrivateKey(req.session.privateKey);
225 const decrypted = decrypt.decrypt(encrypted);
226 console.log(`${TAG} decrypted : ${decrypted}`);
228 const pincode_passed = decrypted === req.session.pincode ? true : false;
229 console.log(`${TAG} pincode result : ${pincode_passed}`);
230 if (pincode_passed) {
231 // The pincode is disposable.
232 req.session.pincode = undefined;
233 if (!req.session.ip || req.session.ip === undefined) {
234 req.session.ip = sip;
236 console.log(`${TAG} pincode passed`);
239 if (tryCount === 5 && !pincode_passed) {
243 res.send(pincode_passed);
247 async function displayPincode(req) {
249 const byteData = crypto.randomBytes(256);
250 req.session.pincode = parseInt(byteData.toString('hex').substr(0, 8), 16).toString().substr(0, 4);
252 await security.awaitKeyPair(req);
253 // Show pincode popup
254 webapis.postPlainNotification("Input Pincode: ", req.session.pincode, 10);
257 var HTTPserverStart = function() {
258 evtEmit = new EventEmitter();
259 const app = express();
260 app.engine('html', require('ejs').renderFile);
261 app.set('view engine', 'ejs');
262 app.set('views', `${g.baseDir}/../`);
263 app.disable('x-powered-by');
264 app.use('/pincode', express.static(`${g.baseDir}/../pincode`));
267 app.use(express.urlencoded({
270 app.use(express.json());
271 // For session management
274 saveUninitialized: true,
275 secret: String(crypto.randomBytes(20)),
282 var sessionChecker = function(req, res, next) {
283 console.log(`${TAG} url : ${req.url}`);
284 console.log(`${TAG} session id : ${req.session.id}`);
285 const rawIp = req.socket.remoteAddress;
286 const ip = rawIp.slice(rawIp.lastIndexOf(":") + 1);
287 console.log(`${TAG} ip : ${ip}`);
289 // The pincode page and local connections are allowed without session.
290 if (req.session.ip !== ip &&
291 req.session.ip === undefined &&
292 !req.url.includes('id=') &&
293 !req.url.includes('/pincode/') &&
294 !non_ip_list.includes(ip)) {
295 console.log(`${TAG} Not valid access`);
296 res.redirect(401, PUBLIC_DOMAIN);
303 app.use(sessionChecker);
305 const appProxy = require('./app_proxy');
306 app.use('/app', appProxy(app, g.port));
307 app.use('/client', clientRouter);
308 console.log(`${TAG} __dirname: ${__dirname}`);
311 platform_app_path = '/opt/usr/apps'
312 console.log(`${TAG} TV Profile`);
315 var tizenApp = tizen.application.getCurrentApplication();
316 console.log(`${TAG} ID, packageId: ${tizenApp.appInfo.id} ${tizenApp.appInfo.packageId}`);
317 serverAppId = tizenApp.appInfo.id.split('.')[0];
318 g.baseDir = __dirname.split(serverAppId)[0];
319 console.log(`${TAG} g.baseDir: ${g.baseDir}`);
321 clientRouter.get('/webclip/*', function(req, res) {
322 let file = req.originalUrl.replace('/client/webclip/', '').replace(/\?.+$/, '');
323 let webclipName = '';
325 const match = file.match(/^[^\/]+/);
327 webclipName = match[0];
329 console.log(`${TAG} webclip name: ${webclipName}`);
331 // find appId by webclip name
332 const app = dataApps.filter(function(app) {
333 return !!app.webclip && app.webclip.manifest.name === webclipName;
336 appId = app.d2dApp.appPkgID;;
339 console.log(`${TAG} root : ${platform_app_path}/${appId}/${TIZEN_WEB_APP_SHARED_RESOURCES}/${WEBCLIP_DIRECTORY}`);
341 root: path.join(platform_app_path, appId, TIZEN_WEB_APP_SHARED_RESOURCES, WEBCLIP_DIRECTORY)
344 // remove weblip name from path
345 file = file.replace(webclipName + '/', '');
346 res.sendFile(file, options, function(err) {
348 console.log(`${TAG} err: ${err}`);
349 res.send("err", err);
351 console.log(`${TAG} res.sendFile() done: ${file}`);
356 clientRouter.get('/updateWebclip', function(req, res) {
357 console.log(`${TAG} get(/updateWebclip)`);
358 const apps = getWebClipsList();
365 console.log(`${TAG} webclip : ${JSON.stringify(result)}`);
369 clientRouter.get('/*', function(req, res) {
370 const file = req.originalUrl.replace('/client/', '').replace(/\?.+$/, '');
371 const pkgId = webapis.getPackageId();
372 const fullPath = require('path').join(g.baseDir, pkgId, '/res/wgt/client', file);
373 console.log(`${TAG} pkgId: ${pkgId}, fullPath: ${fullPath}`);
374 res.sendFile(fullPath);
377 app.get('/', function(req, res) {
378 console.log(`${TAG} URL parameter : ${req.originalUrl}`);
379 urlParam = req.originalUrl;
382 res.redirect("/pincode/pincode.html");
384 if (req.query.roomId !== undefined) {
385 // FIXME: Remove app logic here
386 res.render("client/invited.html");
387 } else if (req.query.pageUrl !== undefined) {
388 webapis.mde.launchBrowserFromUrl(req.query.pageUrl);
391 res.render("client/client.html");
396 app.get('/d2dIcon/*', (req, res) => {
397 let fullPath = req.originalUrl.replace("d2dIcon", platform_app_path);
398 res.sendFile(fullPath);
401 app.get('/appList', (req, res) => {
405 app.get('/updateAppList', (req, res) => {
407 'Content-Type': 'text/event-stream',
408 'Cache-Control': 'no-cache',
409 'Connection': 'keep-alive'
411 evtEmit.on("updateapplist", (event, data) => {
412 res.write("event: " + String(event) + "\n" + "data: " + JSON.stringify(data) + "\n\n");
416 app.get('/pincode/publicKey', async (req, res) => {
418 await displayPincode(req);
419 res.send(req.session.publicKey);
422 app.post('/pincode/pinCodeToServer', express.json(), (req, res) => {
423 // Verify encrypted pincode
424 const resultData = req.body['pincode'];
425 console.log(`${TAG} pinCodeToServer resultData : ${resultData}`);
426 comparePincode(req, res, resultData);
429 app.post('/d2d', (req, res) => {
430 if (req.session.ip !== undefined) {
431 console.log(`${TAG} client.html`);
432 res.render("client/client.html");
434 console.log(`${TAG} no client.html`);
435 res.redirect(401, PUBLIC_DOMAIN);
439 // receive data or cmd to app on device
440 app.post('/app', (req, res) => {
446 app.get('/service', (req, res) => {
449 app.post('/url', (req, res) => {
450 webapis.mde.launchBrowserFromUrl(req.body.url);
453 httpserver = http.createServer(app);
454 httpserver.listen(g.port, function() {
455 const interfaces = require('os').networkInterfaces();
456 for (const devName in interfaces) {
457 if (interfaces.hasOwnProperty(devName)) {
458 const iface = interfaces[devName];
459 for (let i = 0; i < iface.length; i++) {
460 const alias = iface[i];
461 if (alias.family === 'IPv4' && !non_ip_list.includes(alias.address) && !alias.internal)
462 updateDNSresolver(alias.address);
467 relayServer(httpserver);
470 module.exports.getUrlParam = function() {
471 console.log(`${TAG} getUrlParam is called`);
475 module.exports.onStart = function() {
478 setPackageInfoEventListener();
479 console.log(`${TAG} onStart is called in DNS Resolver`);
482 module.exports.onStop = function() {
485 console.log(`${TAG} Server Terminated`);
487 unsetPackageInfoEventListener();
488 evtEmit.off("updateapplist");
489 console.log(`${TAG} onStop is called in DNS Resolver`);