Implement Runtime Add-on
[platform/framework/web/crosswalk-tizen.git] / wrt / src / extension_manager.js
1 'use strict';
2 const fs = require('fs');
3 const path = require('path');
4 const extension_debug = require('debug')('EXTENSION');
5 const MANIFEST_FILE = 'manifest.json';
6 const PRELOAD_JS_FILE = 'preload.js';
7 const EXTENSIONS_DB_FILE = 'extensions_db.json';
8 const T_CRX = 'CRX';
9 const T_WRT = 'WRT';
10 const T_API = 'API';
11 const {BrowserWindow} = require('electron');
12
13 var EXTENSIONS_PATH = process.env.WAS_EXTENSIONS_PATH;
14 if (!EXTENSIONS_PATH) {
15     var resourcePath = __dirname.split('app.asar')[0];
16     extension_debug('WARNING! WAS_EXTENSIONS_PATH not set - extensions cannot be loaded');
17     EXTENSIONS_PATH = path.join(resourcePath, 'runtime_addon');
18     extension_debug('Temporarily set WAS_EXTENSIONS_PATH=' + EXTENSIONS_PATH);
19 }
20
21 class ExtensionManager {
22
23     constructor(ext_path) {
24         if (!ext_path) ext_path = EXTENSIONS_PATH;
25         this.ext_path_ = ext_path;
26         this.extensions_list_ = null;
27         this.extensions_ = null;
28         this.extensions_API_ = null;
29     }
30
31     printAPIs() {
32         extension_debug('==========printAPIs==========');
33         for (var namespace in this.extensions_API_) {
34             extension_debug('namespace: ' + namespace);
35             for (var entry_symbol in this.extensions_API_[namespace]) {
36                 extension_debug('extensions_API[' + namespace + '][' + entry_symbol + '] = ' + this.extensions_API_[namespace][entry_symbol]);
37             }
38         }
39         extension_debug('==========printAPIs==========');
40     }
41
42     loadExtensionsListFromPath() {
43         var extensions_list = [],
44             filenames;
45         try {
46             filenames = fs.readdirSync(this.ext_path_);
47         } catch (e) {
48             extension_debug('LoadExtensionsListFromPath - fs.readdirSync error : ' + e);
49             return false;
50         }
51         if (filenames) {
52             for (var i in filenames) {
53                 var filename = filenames[i],
54                     filepath = path.join(this.ext_path_, filename),
55                     stats = fs.statSync(filepath);
56
57                 if (stats.isDirectory()) {
58                     var manifest_obj,
59                         manifest_path = path.join(filepath, MANIFEST_FILE);
60
61                     try {
62                         manifest_obj = JSON.parse(fs.readFileSync(manifest_path));
63                     } catch (e) {
64                         extension_debug('LoadExtensionsListFromPath - error : ' + e);
65                         continue;
66                     }
67                     var extension = new Object();
68
69                     extension.name = manifest_obj.name;
70                     extension.path = filepath;
71                     if (manifest_obj.type)  {
72                         extension.type = manifest_obj.type;
73                     } else {
74                         extension.type = 'WRT';
75                     }
76                     extension.activate = true; // activate by default
77                     extensions_list.push(extension);
78                 }
79             }
80         }
81         this.extensions_list_ = extensions_list;
82         extension_debug('LoadExtensionsListFromPath ; ' + JSON.stringify(this.extensions_list_));
83         return true;
84     }
85
86     loadJsonDB(db_path) {
87         if (!db_path) {
88             db_path = path.join(this.ext_path_, EXTENSIONS_DB_FILE);
89         }
90         var extensions_list;
91         try {
92             extensions_list = JSON.parse(fs.readFileSync(db_path));
93         } catch (e) {
94             extension_debug('LoadJsonDB - open error : ' + e);
95             return false;
96             // For DEBUG purpose (load extensions from PATH, not via INSTALLER)
97             //return this.loadExtensionsListFromExtPath();
98         }
99         this.extensions_list_ = extensions_list;
100         return true;
101     }
102
103     saveJsonDB(db_path) {
104         if (!db_path) {
105             db_path = path.join(this.ext_path_, EXTENSIONS_DB_FILE);
106         }
107         var fd;
108         try {
109             fd = fs.openSync(db_path, 'w');
110         } catch (e) {
111             extension_debug('SaveJsonDB - open error : ' + e);
112             return false;
113         }
114         fs.writeSync(fd, JSON.stringify(this.extensions_list_));
115         fs.closeSync(fd);
116         return true;
117     }
118
119     generateJsFromAPIs(js_path) {
120         if (!js_path) {
121             js_path = path.join(this.ext_path_, PRELOAD_JS_FILE);
122         }
123         extension_debug('preload.js : ' + js_path);
124         var fd;
125         try {
126             fd = fs.openSync(js_path, 'w');
127         } catch (e) {
128             extension_debug('GenerateJsFromAPIs - open error : ' + e);
129             return false;
130         }
131         // Introduction Comments & preset
132         var comments = '// Auto-generated code by extensions_installer\n// Generated from “entry_points” field in package.json\n// var <entry_points_symbol> = <entry_points_class>;\n';
133         var preset = '\nvar EXTENSIONS_PATH = process.env.WAS_EXTENSIONS_PATH;\n';
134         fs.writeSync(fd, comments + preset);
135         // namespace
136         for (var namespace in this.extensions_API_) {
137             if (namespace != '_default_') {
138                 fs.writeSync(fd, '\n//namespace\n');
139                 fs.writeSync(fd, 'var ' + namespace + ' = new Object();\n');
140                 // namespace.entry_points
141                 fs.writeSync(fd, '\n//namespace.entry_points\n');
142                 for (var entry_symbol in this.extensions_API_[namespace]) {
143                     fs.writeSync(fd, namespace + '.' + entry_symbol + ' = require("' + this.extensions_API_[namespace][entry_symbol] + '");\n');
144                 }
145                 // attach to root
146                 fs.writeSync(fd, '\n//attach to window\n');
147                 fs.writeSync(fd, 'window.' + namespace + ' = ' + namespace + ';\n');
148             } else { // '_default_'
149                 // entry_points and attach to root
150                 fs.writeSync(fd, '\n//default namespace - entry_points and attach to window\n');
151                 /*eslint no-redeclare: 1*/
152                 for (var entry_symbol in this.extensions_API_[namespace]) {
153                     fs.writeSync(fd, 'window.' + entry_symbol + ' = require("' + this.extensions_API_[namespace][entry_symbol] + '");\n');
154                 }
155             }
156         }
157         fs.closeSync(fd);
158         return true;
159     }
160
161     build() {
162         // 0. load extensions_list_ from JSON DB
163         this.loadJsonDB();
164         var extensions = [];
165         var extensions_API = [];
166         // 1. load extensions from extension_list_
167         if (this.extensions_list_) {
168             for (var i in this.extensions_list_) {
169                 var extension = this.extensions_list_[i];
170                 if (extension.activate == false) {
171                     continue;
172                 }
173                 try {
174                     var manifest_obj, manifest_path = path.join(extension.path, MANIFEST_FILE);
175                     manifest_obj = JSON.parse(fs.readFileSync(manifest_path));
176                     extension_debug('manifest_obj : ' + JSON.stringify(manifest_obj));
177                     if (manifest_obj.type && manifest_obj.type.toUpperCase() === 'INSTALLER') {
178                         // do nothing for installer extension
179                         extension_debug('ExtensionManager.build ' + manifest_obj.name + ' is for installer - SKIP');
180                         continue;
181                     } else if (manifest_obj.type && manifest_obj.type.toUpperCase() === 'API') {
182                         var namespace = manifest_obj.namespace;
183                         if (!namespace) {
184                             namespace = '_default_';
185                         }
186                         if (!extensions_API[namespace]) {
187                             extensions_API[namespace] = [];
188                         }
189                         for (var entry_it in manifest_obj.entry_points) {
190                             for (var entry_symbol in manifest_obj.entry_points[entry_it]) {
191                                 var module_path = manifest_obj.entry_points[entry_it][entry_symbol];
192                                 if (extensions_API[namespace][entry_symbol]) {
193                                     extension_debug('extensions_API[' + namespace + '][' + entry_symbol + '] already registered :' + extensions_API[namespace][entry_symbol]);
194                                     continue;
195                                 }
196                                 extensions_API[namespace][entry_symbol] = path.join(extension.path, module_path);
197                                 extension_debug('extensions_API[' + namespace + '][' + entry_symbol + '] registered :' + extensions_API[namespace][entry_symbol]);
198                             }
199                         }
200                     } else {
201                         var type;
202                         if (manifest_obj.main) {
203                             type = T_WRT;
204                         } else {
205                             type = T_CRX;
206                         }
207                         if (!extensions[type]) {
208                             extensions[type] = [];
209                         }
210                         if (extensions[type][manifest_obj.name]) {
211                             extension_debug('extensions[' + type + '][' + manifest_obj.name + '] already registered : ' + extensions[type][manifest_obj.name]);
212                             continue;
213                         }
214                         if (manifest_obj.main) {
215                             extensions[type][manifest_obj.name] = path.join(extension.path, manifest_obj.main);
216                         } else {
217                             extensions[type][manifest_obj.name] = extension.path;
218                         }
219                     }
220                     extension_debug('extensions[' + type + '][' + manifest_obj.name + '] = ' + extensions[type][manifest_obj.name] + ' registered');
221                 } catch (e) {
222                     extension_debug('ExtensionManager.build error - ' + e);
223                 }
224             }
225         }
226         if (this.extensions_ != null) {
227             delete this.extensions_;
228             this.extensions_ = null;
229         }
230         if (this.extensions_API_ != null) {
231             delete this.extensions_API_;
232             this.extensions_API_ = null;
233         }
234         this.extensions_ = extensions;
235         this.extensions_API_ = extensions_API;
236         return this.extensions_;
237     }
238
239     activate(event_emitter, name) {
240         if (!this.extensions_) {
241             return;
242         }
243         var extension, extension_path = null;
244         if (this.extensions_[T_WRT] !== undefined && this.extensions_[T_WRT][name] !== undefined) {
245             extension_path = this.extensions_[T_WRT][name];
246             extension_debug('activate: ' + extension_path + ' name:' + name);
247             try {
248                 extension = require(extension_path);
249             } catch (e) {
250                 extension_debug('activate - error on require() : ' + e);
251                 return;
252             }
253             if (extension && extension.activate) {
254                 extension.activate(event_emitter);
255             }
256             else extension_debug('extension.activate not defined!');
257         } else if (this.extensions_[T_CRX] !== undefined && this.extensions_[T_CRX][name] !== undefined) {
258             extension_path = this.extensions_[T_CRX][name];
259             extension_debug('activate 22: ' + extension_path + ' name:' + name);
260             try {
261                 BrowserWindow.addExtension(extension_path);
262             } catch (e) {
263                 extension_debug('activate - error on addExtension() : ' + e);
264             }
265             return;
266         }
267     }
268
269     deactivate(event_emitter, name) {
270         if (!this.extensions_) {
271             return;
272         }
273         extension_debug('deactivate: name:' + name);
274         var extension, extension_path = null;
275         if (this.extensions_[T_WRT] !== undefined && this.extensions_[T_WRT][name] !== undefined) {
276             try {
277                 extension_path = this.extensions_[T_WRT][name];
278             } catch (e) {
279                 extension_debug('deactivate - error : ' + e);
280                 return;
281             }
282             extension_debug('deactivate: path:' + extension_path);
283             try {
284                 extension = require(extension_path);
285             } catch (e) {
286                 extension_debug('deactivate - error on require() : ' + e);
287             }
288             if (extension && extension.deactivate) {
289                 extension.deactivate(event_emitter);
290             } else {
291                 extension_debug('extension.deactivate not defined!');
292             }
293         } else if (this.extensions_[T_CRX] !== undefined && this.extensions_[T_CRX][name] !== undefined) {
294             extension_path = this.extensions_[T_CRX][name];
295             try {
296                 BrowserWindow.removeExtension(name);
297             } catch (e) {
298                 extension_debug('activate - error on removeExtension() : ' + e);
299             }
300         }
301     }
302
303     activateAll(event_emitter) {
304         if (!this.extensions_) {
305             extension_debug('activateAll - extensions not built');
306             return;
307         }
308         for (var name in this.extensions_[T_WRT]) {
309             this.activate(event_emitter, name);
310         }
311         for (var name in this.extensions_[T_CRX]) {
312             this.activate(event_emitter, name);
313         }
314     }
315
316     deactivateAll(event_emitter) {
317         if (!this.extensions_) {
318             extension_debug('deactivateAll - extensions not built');
319             return;
320         }
321         for (var name in this.extensions_[T_WRT]) {
322             this.deactivate(event_emitter, name);
323         }
324         for (var name in this.extensions_[T_CRX]) {
325             this.deactivate(event_emitter, name);
326         }
327     }
328
329     static getManifestFile() {
330         return MANIFEST_FILE;
331     }
332
333     static getPreloadJsFile() {
334         return PRELOAD_JS_FILE;
335     }
336
337     static getExtensionsPath() {
338         return EXTENSIONS_PATH;
339     }
340 }
341
342 module.exports = ExtensionManager;