ea8ad96494512b8ec8f64304c1de7e6d2f7e1d35
[platform/framework/web/wrtjs.git] / wrt_app / common / wrt_xwalk_extension.ts
1 /*
2  * Copyright (c) 2019 Samsung Electronics Co., Ltd All Rights Reserved
3  *
4  *    Licensed under the Apache License, Version 2.0 (the "License");
5  *    you may not use this file except in compliance with the License.
6  *    You may obtain a copy of the License at
7  *
8  *        http://www.apache.org/licenses/LICENSE-2.0
9  *
10  *    Unless required by applicable law or agreed to in writing, software
11  *    distributed under the License is distributed on an "AS IS" BASIS,
12  *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  *    See the License for the specific language governing permissions and
14  *    limitations under the License.
15  */
16
17 import './exception_handling';
18
19 interface NativeXWalkExtension extends NativeWRTjs.XWalkExtension {
20   loaded?: boolean
21 }
22
23 let instance: XWalkExtension | undefined;
24 let api_: { [key: string]: any } = {};
25 let extensions_: { [key: string]: NativeXWalkExtension } = {};
26
27 class XWalkExtension {
28   constructor() {
29     const binding: NativeWRTjs.XWalkExtensionBinding = process.wrtBinding('wrt_xwalk_extension')
30     var extensions: NativeXWalkExtension[] = binding.getExtensions();
31     for (var i = 0; i < extensions.length; i++) {
32       extensions[i].loaded = false;
33       console.log("Load extension : " + extensions[i].name);
34       extensions_[extensions[i].name] = extensions[i];
35     }
36     for (var name in extensions_) {
37       if (!extensions_[name].use_trampoline) {
38         this.load(extensions_[name]);
39       }
40     }
41     for (var name in extensions_) {
42       if (extensions_[name].use_trampoline) {
43         this.installTrampoline(extensions_[name]);
44       }
45     }
46   }
47
48   /**
49    * Creates namespace for 'name' in given object.
50    * Eg. this.createNamespace(global, 'tizen.contact') will create:
51    * global.tizen.contact = {}
52    *
53    * @param {Object} object
54    * @param {String} name
55    */
56   createNamespace(object: { [key: string]: any }, name: string) {
57     var obj = object;
58     var arr = name.split('.');
59     for (var i = 0; i < arr.length; i++) {
60       obj[arr[i]] = obj[arr[i]] || {};
61       obj = obj[arr[i]];
62     }
63   }
64
65   exposeApi(ext: NativeXWalkExtension) {
66     var i, entry_points, entry_point, tmp, parent_name, base_name;
67
68     // additional entry points are installed in global context by eval()
69     // so we need to move it to protected api_ object first
70     entry_points = [...new Set(ext.entry_points)];
71     for (i = 0; i < entry_points.length; i++) {
72       entry_point = entry_points[i];
73       tmp = entry_point.split('.');
74       parent_name = tmp[0];
75       base_name = tmp[tmp.length - 1];
76
77       api_[parent_name][base_name] = global[parent_name][base_name];
78       delete global[parent_name][base_name];
79     }
80
81     entry_points.push(ext.name);
82
83     for (i = 0; i < entry_points.length; i++) {
84       entry_point = entry_points[i];
85       tmp = entry_point.split('.');
86       parent_name = tmp[0];
87       base_name = tmp[tmp.length - 1];
88
89       Object.defineProperty(global[parent_name], base_name, {
90         get: function (parent_name, base_name) {
91           return function () {
92               return api_[parent_name][base_name];
93           }
94         }(parent_name, base_name),
95         configurable: false,
96         enumerable: true
97       });
98     }
99   }
100
101   static runtimeMessageHandler(type: string, data?: string, callback?: (message: string) => void): void {
102     console.log('This is prototype of runtimeMessageHandler');
103   }
104
105   /**
106    * @param {Object} ext
107    */
108   load(ext: NativeXWalkExtension) {
109     if (ext.loaded)
110       return;
111
112     ext.loadInstance();
113     ext.loaded = true;
114
115     this.createNamespace(api_, ext.name);
116     this.createNamespace(global, ext.name);
117
118     var api = (ext.use_trampoline) ? api_ : global;
119
120     const jscode =
121       '(function(extension) {' +
122       '  extension.internal = {};' +
123       '  extension.internal.sendSyncMessage = extension.sendSyncMessage;' +
124       '  delete extension.sendSyncMessage;' +
125       '  var exports = {}; ' +
126       '  (function() {\'use strict\'; ' + ext.jsapi + '})();' +
127       '  api.' + ext.name + ' = exports; ' +
128       '});';
129
130     try {
131       var func = eval(jscode);
132       func({
133         postMessage: function(msg: string) {
134           return ext.postMessage(msg);
135         },
136         sendSyncMessage: function(msg: string) {
137           return ext.sendSyncMessage(msg);
138         },
139         setMessageListener: function(fn: (message: string) => void) {
140           return ext.setMessageListener(fn);
141         },
142         sendRuntimeMessage: function(type: string, data?: string) {
143           return XWalkExtension.runtimeMessageHandler(type, data);
144         },
145         sendRuntimeSyncMessage: function(type: string, data?: string) {
146           return XWalkExtension.runtimeMessageHandler(type, data);
147         },
148         sendRuntimeAsyncMessage: function(type: string, data?: string, callback?: (message: string) => void) {
149           return XWalkExtension.runtimeMessageHandler(type, data, callback);
150         }
151       });
152
153       if (ext.use_trampoline) {
154         this.exposeApi(ext);
155       }
156     } catch (err) {
157       console.log('Error loading extension "' + ext.name + '": ' + err.message);
158     }
159   }
160
161   /**
162    * This is used to defer extension loading to it's first usage.
163    * Eg. First access to tizen.contact will load extension's 'jsapi' through eval().
164    *
165    * @param {Object} ext
166    */
167   installTrampoline(ext: NativeXWalkExtension) {
168     var entry_points = [...new Set(ext.entry_points)];
169     entry_points.push(ext.name);
170     for (var i = 0; i < entry_points.length; i++) {
171       var tmp = entry_points[i].split('.');
172       var parent_name = tmp[0];
173       var base_name = tmp[tmp.length - 1];
174
175       this.createNamespace(global, entry_points[i]);
176
177       Object.defineProperty(global[parent_name], base_name, {
178         get: function (this: XWalkExtension, parent_name: string, base_name: string) {
179           return function(this: XWalkExtension) {
180             try {
181               this.deleteTrampoline(ext);
182               this.load(ext);
183               return api_[parent_name][base_name];
184             } catch (e) {
185               console.log(e.stack);
186             }
187           }.bind(this);
188         }.call(this, parent_name, base_name),
189         enumerable: true
190       });
191     }
192   }
193
194   deleteTrampoline(ext: NativeXWalkExtension) {
195     var entry_points = [...new Set(ext.entry_points)];
196     entry_points.push(ext.name);
197
198     for (var i = 0; i < entry_points.length; i++) {
199       var tmp = entry_points[i].split('.');
200       var parent_name = tmp[0];
201       var base_name = tmp[tmp.length - 1];
202       delete global[parent_name][base_name];
203     }
204   }
205 }
206
207 export const initialize = () => {
208   if (!instance)
209     instance = new XWalkExtension;
210 }
211
212 export const setRuntimeMessageHandler = (handler: (type: string, data?: string, callback?: (message: string) => void) => void) => {
213   XWalkExtension.runtimeMessageHandler = handler;
214 }
215
216 export let cleanup = () => {
217   delete global.tizen;
218   instance = undefined;
219 }
220
221 export const preventCleanup = () => {
222   cleanup = () => {};
223 }