[Service] Apply sendSyncMessageWith* API
[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       '  extension.internal.sendSyncMessageWithStringReply = extension.sendSyncMessageWithStringReply;' +
131       '  extension.internal.sendSyncMessageWithBinaryReply = extension.sendSyncMessageWithBinaryReply;' +
132       '  delete extension.sendSyncMessage;' +
133       '  delete extension.sendSyncMessageWithStringReply;' +
134       '  delete extension.sendSyncMessageWithBinaryReply;' +
135       '  var exports = {}; ' +
136       '  (function() {\'use strict\'; ' + extension_api + '})();' +
137       '  api.' + ext.name + ' = exports; ' +
138       '});';
139
140     try {
141       var func = eval(jscode);
142       func({
143         postMessage: function(msg: string) {
144           return ext.postMessage(msg);
145         },
146         sendSyncMessage: function(msg: string) {
147           return ext.sendSyncMessage(msg);
148         },
149         sendSyncMessageWithStringReply: function(msg: string) {
150           return ext.sendSyncMessageWithStringReply(msg);
151         },
152         sendSyncMessageWithBinaryReply: function(msg: string) {
153           return ext.sendSyncMessageWithBinaryReply(msg);
154         },
155         setMessageListener: function(fn: (message: string) => void) {
156           return ext.setMessageListener(fn);
157         },
158         sendRuntimeMessage: function(type: string, data?: string) {
159           return XWalkExtension.runtimeMessageHandler(type, data);
160         },
161         sendRuntimeSyncMessage: function(type: string, data?: string) {
162           return XWalkExtension.runtimeMessageHandler(type, data);
163         },
164         sendRuntimeAsyncMessage: function(type: string, data?: string, callback?: (message: string) => void) {
165           return XWalkExtension.runtimeMessageHandler(type, data, callback);
166         }
167       });
168
169       if (ext.use_trampoline) {
170         this.exposeApi(ext);
171       }
172     } catch (err) {
173       console.log('Error loading extension "' + ext.name + '": ' + err.message);
174     }
175   }
176
177   /**
178    * This is used to defer extension loading to it's first usage.
179    * Eg. First access to tizen.contact will load extension's 'jsapi' through eval().
180    *
181    * @param {Object} ext
182    */
183   installTrampoline(ext: NativeXWalkExtension) {
184     var entry_points = [...new Set(ext.entry_points)];
185     entry_points.push(ext.name);
186     for (var i = 0; i < entry_points.length; i++) {
187       var tmp = entry_points[i].split('.');
188       var parent_name = tmp[0];
189       var base_name = tmp[tmp.length - 1];
190
191       this.createNamespace(global, entry_points[i]);
192
193       Object.defineProperty(global[parent_name], base_name, {
194         get: function (this: XWalkExtension, parent_name: string, base_name: string) {
195           return function(this: XWalkExtension) {
196             try {
197               this.deleteTrampoline(ext);
198               this.load(ext);
199               return api_[parent_name][base_name];
200             } catch (e) {
201               console.log(e.stack);
202             }
203           }.bind(this);
204         }.call(this, parent_name, base_name),
205         enumerable: true
206       });
207     }
208   }
209
210   deleteTrampoline(ext: NativeXWalkExtension) {
211     var entry_points = [...new Set(ext.entry_points)];
212     entry_points.push(ext.name);
213
214     for (var i = 0; i < entry_points.length; i++) {
215       var tmp = entry_points[i].split('.');
216       var parent_name = tmp[0];
217       var base_name = tmp[tmp.length - 1];
218       delete global[parent_name][base_name];
219     }
220   }
221 }
222
223 export const initialize = () => {
224   if (!instance)
225     instance = new XWalkExtension;
226 }
227
228 export const setRuntimeMessageHandler = (handler: (type: string, data?: string, callback?: (message: string) => void) => void) => {
229   XWalkExtension.runtimeMessageHandler = handler;
230 }
231
232 export let cleanup = () => {
233   delete global.tizen;
234   instance = undefined;
235 }
236
237 export const preventCleanup = () => {
238   cleanup = () => {};
239 }