[Service] Expose app id related APIs to public and add getCallerAppId
[platform/framework/web/wrtjs.git] / wrt_app / common / service_manager.ts
1 const Module = require('module');
2 import { TimerManager } from '../service/timer_manager';
3 import * as XWalkExtension from './wrt_xwalk_extension';
4 import * as vm from 'vm';
5 import { wrt } from '../browser/wrt';
6 import { DeviceAPIRouter } from '../service/device_api_router';
7
8 interface ContextMap {
9   [id: string]: vm.Context;
10 }
11
12 interface ContextOption {
13   [key: string]: any;
14 }
15
16 let sandbox: ContextMap = {};
17 let internal_handler: ContextOption = {};
18 let service_type: string = wrt.getServiceModel?.() ?? 'UI';
19
20 function requestStopService(id: string) {
21   console.log(`${id} will be closed`);
22   setTimeout(() => wrt.stopService(id), 500);
23 }
24
25 function callFunctionInContext(name: string, id: string) {
26   try {
27     const script = `if (typeof ${name} === 'function') { ${name}(); }`;
28     vm.runInContext(script, sandbox[id]);
29   } catch (e) {
30     console.log(`${name} has exception: ${e}`);
31     if (wrt.tv) {
32       requestStopService(id);
33     }
34   }
35 }
36
37 export function startService(id: string, filename?: string) {
38   if (sandbox[id] === undefined) {
39     XWalkExtension.initialize();
40     XWalkExtension.setRuntimeMessageHandler((type, data) => {
41       if (type === 'tizen://exit') {
42         requestStopService(id);
43       }
44     });
45     sandbox[id] = {
46       console: console,
47       module: new Module,
48       require: require,
49       tizen: global.tizen,
50       webapis: wrt.tv ? global.webapis : global.webapis = {},
51     };
52     sandbox[id].module.exports.onStop = () => {
53       callFunctionInContext('module.exports.onExit', id);
54     };
55     let ids = id.split(':');
56     let caller_app_id = ids[1] ?? '';
57     sandbox[id].webapis.getCallerAppId = () => {
58       return caller_app_id;
59     }
60     let service_id = ids[0];
61     sandbox[id].webapis.getServiceId = () => {
62       return service_id;
63     }
64     sandbox[id].webapis.getPackageId = () => {
65       let app_info = global.tizen.application.getAppInfo(service_id);
66       if (app_info)
67         return app_info.packageId;
68       return ids[0].split('.')[0];
69     }
70
71     if (service_type !== 'UI') {
72       const permissions = wrt.getPrivileges(id);
73       console.log(`permissions : ${permissions}`);
74       const AccessControlManager = require('../service/access_control_manager');
75       AccessControlManager.initialize(permissions, sandbox[id]);
76     }
77     for (let key in global)
78       sandbox[id][key] = global[key];
79
80     internal_handler[id] = {};
81     internal_handler[id].timer_manager = new TimerManager();
82     const timer_api = internal_handler[id].timer_manager.getTimerAPI();
83     for (let key in timer_api)
84       sandbox[id][key] = timer_api[key];
85
86     let object_list = [ 'Error', 'EvalError', 'RangeError', 'ReferenceError',
87         'SyntaxError', 'TypeError', 'URIError', 'Number', 'BigInt', 'Math', 'Date',
88         'String', 'RegExp', 'Array', 'Int8Array', 'Uint8Array', 'Uint8ClampedArray',
89         'Int16Array', 'Uint16Array', 'Int32Array', 'Uint32Array', 'Float32Array',
90         'Float64Array', 'BigInt64Array', 'BigUint64Array', 'Map', 'Set', 'WeakMap',
91         'WeakSet', 'ArrayBuffer', 'DataView', 'JSON', 'Promise', 'Reflect', 'Proxy',
92         'Intl', 'WebAssembly', 'Boolean', 'Function', 'Object', 'Symbol' ];
93     for (let prop of object_list)
94       sandbox[id][prop] = global[prop];
95
96     let options: ContextOption = {};
97     let code;
98     if (service_type !== 'UI') {
99       options.filename = id;
100       if (wrt.tv) {
101         let extension_resolver = function (module: any, file_path: string) {
102           console.log(`resolved path: ${file_path}`);
103           let content = (wrt.tv as NativeWRTjs.TVExtension).decryptFile(id, file_path);
104           if (content) {
105             // Remove BOM
106             if (content.charCodeAt(0)  === 0xFEFF)
107               content = content.slice(1);
108             module._compile(content, file_path);
109           }
110         };
111         sandbox[id].require.extensions['.js.spm'] = extension_resolver;
112         sandbox[id].require.extensions['.spm'] = extension_resolver;
113       }
114       filename = wrt.getStartServiceFile(id);
115       console.log(`start global service file: ${filename}`);
116     }
117     code = `const app = require('${filename}')`;
118     if (service_type === 'DAEMON') {
119       internal_handler[id].deivce_api_router = new DeviceAPIRouter(sandbox[id]);
120     }
121     vm.runInNewContext(code, sandbox[id], options);
122   }
123
124   if (sandbox[id]['started'] === undefined) {
125     sandbox[id]['started'] = true;
126     sandbox[id]['stopped'] = undefined;
127     callFunctionInContext('app.onStart', id);
128     if (service_type !== 'UI')
129       wrt.finishStartingService(id);
130   } else {
131     console.log(id + ' service has been started.');
132   }
133   callFunctionInContext('app.onRequest', id);
134 }
135
136 export function stopService(id: string) {
137   console.log('stopService')
138   if (sandbox[id]['stopped']) {
139     console.log(id + ' service has been already stopped.');
140     return;
141   }
142
143   sandbox[id]['stopped'] = true;
144   sandbox[id]['started'] = undefined;
145   callFunctionInContext('app.onStop', id);
146
147   internal_handler[id].timer_manager.releaseRemainingTimers();
148   for (let key in sandbox[id])
149     delete sandbox[id][key];
150   delete sandbox[id];
151   for (let key in internal_handler[id])
152     delete internal_handler[id][key];
153   delete internal_handler[id];
154
155   if (Object.keys(sandbox).length === 0)
156     XWalkExtension.cleanup();
157 }