1 const path = require('path');
2 const fs = require('fs');
3 const express = require('express');
4 const QRCode = require('qrcode');
5 const Edge = require('./edge');
6 const SocketTizen = require('./socket-tizen');
7 const { getMyAddress } = require('./util');
9 const TAG = 'service.js';
11 const app = express();
14 key: fs.readFileSync(path.resolve(__dirname, 'key.pem')),
15 cert: fs.readFileSync(path.resolve(__dirname, 'cert.pem'))
18 console.log(TAG, `platform : ${process.platform}`);
20 const httpPort = process.env.HTTP_PORT || 9559;
21 const httpsPort = process.env.PORT || process.env.HTTPS_PORT || 5443;
22 const httpsServer = require('https').createServer(options, app);
23 const httpServer = require('http').createServer(app);
24 const io = require('socket.io')();
25 const isTizen = process.platform === 'tizen';
26 const supportMessagePort = isTizen;
27 // Implementation for edge orchestration
28 const supportEdgeOrchestration =
29 typeof webapis !== 'undefined' && Object.hasOwnProperty.call(webapis, 'edge');
30 console.log(TAG, `supportEdgeOrchestration : ${supportEdgeOrchestration}`);
32 io.attach(httpServer);
33 io.attach(httpsServer);
35 const clients = new Set();
36 const workers = new Map();
37 const sockets = new Map();
38 let edgeForCastanets = null;
39 let forceQuitTimer = null;
40 let isMeerkatStarted = false;
41 let statusIntervalId = null;
44 app.set('host', '0.0.0.0');
47 app.use(express.static(path.join(__dirname, './public')));
48 app.use(express.static(path.join(__dirname, '../../shared/res')));
52 express.static(path.join(__dirname, '../../offload/apps/web/offload.html'))
54 // Host offload-worker
57 express.static(path.join(__dirname, '../../offload-worker/apps/web/'))
60 '/offload-worker/offload-worker.js',
61 express.static(path.join(__dirname, '../../dist/offload-worker.js'))
65 express.static(path.join(__dirname, '../../offload-worker/apps/web/face.webm'))
67 const serveIndex = require('serve-index');
70 express.static(path.join(__dirname, '../../dist')),
71 express.static(path.join(__dirname, '../../offload/sample')),
72 serveIndex(path.join(__dirname, '../../offload/sample'))
76 express.static(path.join(__dirname, '../../test')),
77 serveIndex(path.join(__dirname, '../../test'))
81 function onConnection(socket) {
82 if (isTizen && !isMeerkatStarted) {
84 console.log(TAG, 'Try to start Meerkat client.');
85 tizen.application.launch(
86 'org.tizen.meerkat.client',
88 console.log(TAG, 'Meerkat client is started.');
89 isMeerkatStarted = true;
91 err => console.error(TAG, 'Failed to launch Meerkat client. ' + err)
94 console.error(TAG, 'Failed to launch Meerkat client. ' + err);
97 console.log(TAG, `connection from '${socket.id}.`);
98 sockets.set(socket.id, socket);
100 // client creates a session.
101 socket.on('create', async function () {
102 if (forceQuitTimer !== null) {
103 clearTimeout(forceQuitTimer);
106 if (clients.has(socket.id)) {
107 console.log(TAG, `already created by ${socket.id}.`);
110 clients.add(socket.id);
113 const myAddress = getMyAddress();
116 qr = await QRCode.toDataURL(
117 'https://' + myAddress + ':5443/offload-worker.html'
120 console.error(TAG, 'unabled to generate QR: ' + err);
124 socket.emit('greeting', {
126 workers: Array.from(workers)
131 `[client] session created by ${socket.id}. workers.size : ${workers.size}`
134 if (supportEdgeOrchestration) {
137 Array.from(edgeForCastanets.getCapabilities())
142 socket.on('getcapabilities', function () {
143 console.log(TAG, 'getcapabilities');
144 if (supportEdgeOrchestration) {
147 Array.from(edgeForCastanets.getCapabilities())
150 socket.emit('capabilities', []);
154 socket.on('requestService', function (workerId) {
155 if (supportEdgeOrchestration) {
156 edgeForCastanets.requestService(workerId);
160 // new worker has been joined.
161 socket.on('join', function (worker) {
162 if (supportEdgeOrchestration) {
163 let deviceIp = socket.request.connection.remoteAddress;
164 if (deviceIp.indexOf('::ffff:') !== -1) {
165 deviceIp = deviceIp.substr(7, deviceIp.length);
169 edgeForCastanets.joinDevice(deviceIp);
173 workers.set(worker.id, {
176 features: worker.features,
177 mediaDeviceInfos: worker.mediaDeviceInfos,
182 `worker[${workers.size}] join: '${worker.id}' - '${socket.id}', '${worker.name}'`,
186 for (const client of clients) {
187 const clientSocket = sockets.get(client);
188 clientSocket.emit('worker', {
193 features: worker.features,
194 mediaDeviceInfos: worker.mediaDeviceInfos
199 // route message between clients.
200 socket.on('message', function (data) {
201 console.log(TAG, `message ${JSON.stringify(data)}`);
203 if (workers.has(data.to)) {
204 socketId = workers.get(data.to).socketId;
205 } else if (clients.has(data.to)) {
210 const socket = sockets.get(socketId);
211 socket.emit('message', data);
215 socket.on('disconnect', function (reason) {
216 const socketId = socket.id;
217 sockets.delete(socketId);
218 if (clients.has(socketId)) {
219 console.log(TAG, `[client] session terminated by client: ${socketId}`);
220 clients.delete(socketId);
222 // broadcast to offload-worker
223 for (const socket of sockets.values()) {
224 socket.emit('client', {
230 if (clients.size === 0) {
231 if (supportMessagePort) {
234 forceQuitTimer = setTimeout(function () {
237 'All clients are destroyed. Broadcast \'forceQuit\' to workers'
239 for (const socket of sockets.values()) {
240 socket.emit('client', {
248 if (supportEdgeOrchestration) {
249 let deviceIp = socket.request.connection.remoteAddress;
250 if (deviceIp.indexOf('::ffff:') !== -1) {
251 deviceIp = deviceIp.substr(7, deviceIp.length);
255 edgeForCastanets.disconnectDevice(deviceIp);
260 workers.forEach(function (value, key, map) {
261 if (value.socketId === socket.id) {
267 for (const client of clients) {
268 const socket = sockets.get(client);
269 socket.emit('worker', {
275 workers.delete(workerId);
276 console.log(TAG, `worker[${workers.size}] bye: '${workerId}'`);
282 io.of('/offload-js').on('connection', onConnection);
284 if (supportEdgeOrchestration) {
285 edgeForCastanets = new Edge(
288 'com.samsung.android.castanets'
292 function startServer() {
293 console.log(TAG, 'starting server...');
295 if (!httpsServer.listening) {
296 httpsServer.listen(httpsPort, function () {
297 console.log(TAG, `server is listening on https ${httpsPort} port.`);
301 if (!httpServer.listening) {
302 httpServer.listen(httpPort, function () {
303 console.log(TAG, `server is listening on http ${httpPort} port.`);
308 function closeServer() {
309 console.log(TAG, 'closing server...');
311 if (httpsServer.listening) {
312 httpsServer.close(err => {
314 console.error('failed to close the https server:', err);
319 if (httpServer.listening) {
320 httpServer.close(err => {
322 console.error('failed to close the http server:', err);
328 // Check the client status
329 function checkConnectionStatus() {
330 for (const client of clients) {
331 const socket = sockets.get(client);
332 if (socket.constructor === SocketTizen) {
334 socket.emit('status');
336 console.error(TAG, `Failed to check ${client} status`);
337 socket.handleEvents('disconnect');
343 function handleMessagePort(messages) {
344 if (messages.length === 0) {
345 console.error(TAG, 'Not found message');
349 const message = messages[0];
350 const event = message.key;
351 const value = JSON.parse(message.value);
354 if (event === 'connect') {
355 // FIXME: The message port does not guarantee that the connection has
356 // been disconnected when the page is reloaded. Therefore, if a new
357 // connection occurs with the same id, the existing connection is
359 if (sockets.has(id)) {
360 console.log(TAG, `Disconnect already connected socket: ${id}`);
361 const socket = sockets.get(id);
362 socket.handleEvents('disconnect');
365 const socket = new SocketTizen(id, localPort);
366 socket.on('connection', onConnection);
368 sockets.set(id, socket);
371 const socket = sockets.get(id);
372 socket.handleEvents(event, value.data);
376 module.exports.onStart = () => {
377 console.log(`${TAG} onStart is called`);
378 if (supportMessagePort) {
379 console.log(TAG, 'listening tizen messageport...');
380 localPort = tizen.messageport.requestLocalMessagePort('offload');
381 localPort.addMessagePortListener(handleMessagePort);
383 // Prevent to terminate the process
384 statusIntervalId = setInterval(checkConnectionStatus, 1000);
390 module.exports.onStop = () => {
391 console.log(`${TAG} onStop is called`);
392 if (supportMessagePort) {
393 for (const socket of sockets.values()) {
396 if (localPort !== null) {
397 localPort.removeMessagePortListener(handleMessagePort);
399 if (statusIntervalId !== null) {
400 clearInterval(statusIntervalId);
407 module.exports.onRequest = () => {
408 console.log(`${TAG} onRequest is called`);