187082aa8c78c5e7ea42e3efc3c2a6b6f3b9afcf
[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 global.window = global.window ?? global;
27
28 class XWalkExtension {
29   constructor() {
30     const binding: NativeWRTjs.XWalkExtensionBinding = process.wrtBinding('wrt_xwalk_extension')
31     var extensions: NativeXWalkExtension[] = binding.getExtensions();
32     for (var i = 0; i < extensions.length; i++) {
33       extensions[i].loaded = false;
34       console.log("Load extension : " + extensions[i].name);
35       extensions_[extensions[i].name] = extensions[i];
36     }
37     for (var name in extensions_) {
38       if (!extensions_[name].use_trampoline) {
39         this.load(extensions_[name]);
40       }
41     }
42     for (var name in extensions_) {
43       if (extensions_[name].use_trampoline) {
44         this.installTrampoline(extensions_[name]);
45       }
46     }
47   }
48
49   /**
50    * Creates namespace for 'name' in given object.
51    * Eg. this.createNamespace(global, 'tizen.contact') will create:
52    * global.tizen.contact = {}
53    *
54    * @param {Object} object
55    * @param {String} name
56    */
57   createNamespace(object: { [key: string]: any }, name: string) {
58     var obj = object;
59     var arr = name.split('.');
60     for (var i = 0; i < arr.length; i++) {
61       obj[arr[i]] = obj[arr[i]] || {};
62       obj = obj[arr[i]];
63     }
64   }
65
66   exposeApi(ext: NativeXWalkExtension) {
67     var i, entry_points, entry_point, tmp, parent_name, base_name;
68
69     // additional entry points are installed in global context by eval()
70     // so we need to move it to protected api_ object first
71     entry_points = [...new Set(ext.entry_points)];
72     for (i = 0; i < entry_points.length; i++) {
73       entry_point = entry_points[i];
74       tmp = entry_point.split('.');
75       parent_name = tmp[0];
76       base_name = tmp[tmp.length - 1];
77
78       api_[parent_name][base_name] = global[parent_name][base_name];
79       delete global[parent_name][base_name];
80     }
81
82     entry_points.push(ext.name);
83
84     for (i = 0; i < entry_points.length; i++) {
85       entry_point = entry_points[i];
86       tmp = entry_point.split('.');
87       parent_name = tmp[0];
88       base_name = tmp[tmp.length - 1];
89
90       Object.defineProperty(global[parent_name], base_name, {
91         get: function (parent_name, base_name) {
92           return function () {
93               return api_[parent_name][base_name];
94           }
95         }(parent_name, base_name),
96         configurable: false,
97         enumerable: true
98       });
99     }
100   }
101
102   static runtimeMessageHandler(type: string, data?: string, callback?: (message: string) => void): void {
103     console.log('This is prototype of runtimeMessageHandler');
104   }
105
106   /**
107    * @param {Object} ext
108    */
109   load(ext: NativeXWalkExtension) {
110     if (ext.loaded)
111       return;
112
113     ext.loadInstance();
114     ext.loaded = true;
115
116     this.createNamespace(api_, ext.name);
117     this.createNamespace(global, ext.name);
118
119     var api = (ext.use_trampoline) ? api_ : global;
120     var extension_api = ext.jsapi;
121     if (global.serviceType === 'GLOBAL' && ext.name === 'xwalk') {
122       console.log(`Delete freeze exports.utils for override method`);
123       extension_api = extension_api.replace('Object.freeze(exports.utils);', '');
124       extension_api = extension_api.replace('Object.freeze(Utils.prototype);', '');
125     }
126     const jscode =
127       '(function(extension) {' +
128       '  extension.internal = {};' +
129       '  extension.internal.sendSyncMessage = extension.sendSyncMessage;' +
130       '  delete extension.sendSyncMessage;' +
131       '  var exports = {}; ' +
132       '  (function() {\'use strict\'; ' + extension_api + '})();' +
133       '  api.' + ext.name + ' = exports; ' +
134       '});';
135
136     try {
137       var func = eval(jscode);
138       func({
139         postMessage: function(msg: string) {
140           return ext.postMessage(msg);
141         },
142         sendSyncMessage: function(msg: string) {
143           return ext.sendSyncMessage(msg);
144         },
145         setMessageListener: function(fn: (message: string) => void) {
146           return ext.setMessageListener(fn);
147         },
148         sendRuntimeMessage: function(type: string, data?: string) {
149           return XWalkExtension.runtimeMessageHandler(type, data);
150         },
151         sendRuntimeSyncMessage: function(type: string, data?: string) {
152           return XWalkExtension.runtimeMessageHandler(type, data);
153         },
154         sendRuntimeAsyncMessage: function(type: string, data?: string, callback?: (message: string) => void) {
155           return XWalkExtension.runtimeMessageHandler(type, data, callback);
156         }
157       });
158
159       if (ext.use_trampoline) {
160         this.exposeApi(ext);
161       }
162     } catch (err) {
163       console.log('Error loading extension "' + ext.name + '": ' + err.message);
164     }
165   }
166
167   /**
168    * This is used to defer extension loading to it's first usage.
169    * Eg. First access to tizen.contact will load extension's 'jsapi' through eval().
170    *
171    * @param {Object} ext
172    */
173   installTrampoline(ext: NativeXWalkExtension) {
174     var entry_points = [...new Set(ext.entry_points)];
175     entry_points.push(ext.name);
176     for (var i = 0; i < entry_points.length; i++) {
177       var tmp = entry_points[i].split('.');
178       var parent_name = tmp[0];
179       var base_name = tmp[tmp.length - 1];
180
181       this.createNamespace(global, entry_points[i]);
182
183       Object.defineProperty(global[parent_name], base_name, {
184         get: function (this: XWalkExtension, parent_name: string, base_name: string) {
185           return function(this: XWalkExtension) {
186             try {
187               this.deleteTrampoline(ext);
188               this.load(ext);
189               return api_[parent_name][base_name];
190             } catch (e) {
191               console.log(e.stack);
192             }
193           }.bind(this);
194         }.call(this, parent_name, base_name),
195         enumerable: true
196       });
197     }
198   }
199
200   deleteTrampoline(ext: NativeXWalkExtension) {
201     var entry_points = [...new Set(ext.entry_points)];
202     entry_points.push(ext.name);
203
204     for (var i = 0; i < entry_points.length; i++) {
205       var tmp = entry_points[i].split('.');
206       var parent_name = tmp[0];
207       var base_name = tmp[tmp.length - 1];
208       delete global[parent_name][base_name];
209     }
210   }
211 }
212
213 export const initialize = () => {
214   if (!instance)
215     instance = new XWalkExtension;
216 }
217
218 export const setRuntimeMessageHandler = (handler: (type: string, data?: string, callback?: (message: string) => void) => void) => {
219   XWalkExtension.runtimeMessageHandler = handler;
220 }
221
222 export let cleanup = () => {
223   delete global.tizen;
224   instance = undefined;
225 }
226
227 export const preventCleanup = () => {
228   cleanup = () => {};
229 }