Merge branch 'tizen_3.0' into tizen_4.0
[platform/core/api/webapi-plugins.git] / src / messageport / messageport_api.js
1 /*
2  * Copyright (c) 2015 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 var JSON_ = xwalk.JSON;
18 var validator_ = xwalk.utils.validator;
19 var types_ = validator_.Types;
20 var type_ = xwalk.utils.type;
21 var converter_ = xwalk.utils.converter;
22 var privUtils_ = xwalk.utils;
23
24 var callbackId = 0;
25 var callbacks = {};
26 var ports = [];
27
28 extension.setMessageListener(function(json) {
29     var msg = JSON_.parse(json);
30     var listeners = callbacks[msg['local_port_id']];
31     var rmp;
32
33     privUtils_.log('Listeners length:' + listeners.length);
34
35     if (!msg.hasOwnProperty('remotePort')) rmp = null;
36     else rmp = new RemoteMessagePort(msg.remotePort, msg.remoteAppId, msg.trusted);
37     for (var i = 0; i < listeners.length; i++) {
38         setTimeout(
39             function(f) {
40                 f(msg.message, rmp);
41             }.bind(null, listeners[i][0]),
42             0
43         );
44     }
45 });
46
47 function nextCallbackId() {
48     return callbackId++;
49 }
50
51 var ExceptionMap = {
52     UnknownError: WebAPIException.UNKNOWN_ERR,
53     TypeMismatchError: WebAPIException.TYPE_MISMATCH_ERR,
54     InvalidValuesError: WebAPIException.INVALID_VALUES_ERR,
55     IOError: WebAPIException.IO_ERR,
56     ServiceNotAvailableError: WebAPIException.SERVICE_NOT_AVAILABLE_ERR,
57     SecurityError: WebAPIException.SECURITY_ERR,
58     NetworkError: WebAPIException.NETWORK_ERR,
59     NotSupportedError: WebAPIException.NOT_SUPPORTED_ERR,
60     NotFoundError: WebAPIException.NOT_FOUND_ERR,
61     InvalidAccessError: WebAPIException.INVALID_ACCESS_ERR,
62     AbortError: WebAPIException.ABORT_ERR,
63     QuotaExceededError: WebAPIException.QUOTA_EXCEEDED_ERR
64 };
65
66 function callNative(cmd, args) {
67     var json = { cmd: cmd, args: args };
68     var argjson = JSON_.stringify(json);
69     var resultString = extension.internal.sendSyncMessage(argjson);
70     var result = JSON_.parse(resultString);
71
72     if (typeof result !== 'object') {
73         throw new WebAPIException(WebAPIException.UNKNOWN_ERR);
74     }
75
76     if (result['status'] == 'success') {
77         if (result['result']) {
78             return result['result'];
79         }
80         return true;
81     } else if (result['status'] == 'error') {
82         var err = result['error'];
83         if (err) {
84             if (ExceptionMap[err.name]) {
85                 throw new WebAPIException(ExceptionMap[err.name], err.message);
86             } else {
87                 throw new WebAPIException(WebAPIException.UNKNOWN_ERR, err.message);
88             }
89         }
90         return false;
91     }
92 }
93
94 function callNativeWithCallback(cmd, args, callback) {
95     if (callback) {
96         var id = nextCallbackId();
97         args['callbackId'] = id;
98         callbacks[id] = callback;
99     }
100
101     return callNative(cmd, args);
102 }
103
104 function SetReadOnlyProperty(obj, n, v) {
105     Object.defineProperty(obj, n, { value: v, writable: false });
106 }
107
108 function MessagePortManager() {
109     // constructor of MessagePortManager
110 }
111
112 MessagePortManager.prototype.requestLocalMessagePort = function(localMessagePortName) {
113     var args = validator_.validateArgs(arguments, [
114         { name: 'localMessagePortName', type: types_.STRING }
115     ]);
116
117     if ('' === args.localMessagePortName) {
118         throw new WebAPIException(
119             WebAPIException.INVALID_VALUES_ERR,
120             'Port name cannot be empty.'
121         );
122     }
123
124     var localPortId;
125     var nativeParam = {
126         localMessagePortName: args.localMessagePortName
127     };
128
129     try {
130         localPortId = callNative(
131             'MessagePortManager_requestLocalMessagePort',
132             nativeParam
133         );
134     } catch (e) {
135         throw e;
136     }
137
138     var returnObject = new LocalMessagePort(args.localMessagePortName, false);
139     ports[nativeParam.localMessagePortName] = localPortId;
140
141     return returnObject;
142 };
143
144 MessagePortManager.prototype.requestTrustedLocalMessagePort = function(
145     localMessagePortName
146 ) {
147     var args = validator_.validateArgs(arguments, [
148         { name: 'localMessagePortName', type: types_.STRING }
149     ]);
150
151     if ('' === args.localMessagePortName) {
152         throw new WebAPIException(
153             WebAPIException.INVALID_VALUES_ERR,
154             'Port name cannot be empty.'
155         );
156     }
157
158     var nativeParam = {
159         localMessagePortName: args.localMessagePortName
160     };
161
162     try {
163         var localPortId = callNative(
164             'MessagePortManager_requestTrustedLocalMessagePort',
165             nativeParam
166         );
167     } catch (e) {
168         throw e;
169     }
170
171     var returnObject = new LocalMessagePort(args.localMessagePortName, true);
172     ports[nativeParam.localMessagePortName] = localPortId;
173
174     return returnObject;
175 };
176
177 MessagePortManager.prototype.requestRemoteMessagePort = function(
178     appId,
179     remoteMessagePortName
180 ) {
181     var args = validator_.validateArgs(arguments, [
182         { name: 'appId', type: types_.STRING },
183         { name: 'remoteMessagePortName', type: types_.STRING }
184     ]);
185
186     var nativeParam = {
187         appId: args.appId,
188         remoteMessagePortName: args.remoteMessagePortName
189     };
190
191     try {
192         var syncResult = callNative(
193             'MessagePortManager_requestRemoteMessagePort',
194             nativeParam
195         );
196     } catch (e) {
197         throw e;
198     }
199
200     var returnObject = new RemoteMessagePort(
201         args.remoteMessagePortName,
202         args.appId,
203         false
204     );
205
206     return returnObject;
207 };
208
209 MessagePortManager.prototype.requestTrustedRemoteMessagePort = function(
210     appId,
211     remoteMessagePortName
212 ) {
213     var args = validator_.validateArgs(arguments, [
214         { name: 'appId', type: types_.STRING },
215         { name: 'remoteMessagePortName', type: types_.STRING }
216     ]);
217
218     var nativeParam = {
219         appId: args.appId,
220         remoteMessagePortName: args.remoteMessagePortName
221     };
222
223     try {
224         var syncResult = callNative(
225             'MessagePortManager_requestTrustedRemoteMessagePort',
226             nativeParam
227         );
228     } catch (e) {
229         throw e;
230     }
231
232     var returnObject = new RemoteMessagePort(
233         args.remoteMessagePortName,
234         args.appId,
235         true
236     );
237
238     return returnObject;
239 };
240
241 function LocalMessagePort(messagePortName, isTrusted) {
242     Object.defineProperties(this, {
243         messagePortName: { value: messagePortName, writable: false, enumerable: true },
244         isTrusted: { value: !!isTrusted, writable: false, enumerable: true }
245     });
246 }
247
248 LocalMessagePort.prototype.addMessagePortListener = function(listener) {
249     var args = validator_.validateArgs(arguments, [
250         { name: 'listener', type: types_.FUNCTION, nullable: false }
251     ]);
252
253     var portId = ports[this.messagePortName];
254
255     if (!callbacks.hasOwnProperty(portId)) callbacks[portId] = [];
256
257     callbackId++;
258     callbacks[portId].push([listener, callbackId]);
259
260     return callbackId;
261 };
262
263 LocalMessagePort.prototype.removeMessagePortListener = function(watchId) {
264     var args = validator_.validateArgs(arguments, [
265         { name: 'watchId', type: types_.LONG, nullable: false, optional: false }
266     ]);
267
268     var toDelete;
269     var listeners = callbacks[ports[this.messagePortName]];
270
271     for (var key in listeners) {
272         var listenerId = listeners[key][1];
273         if (watchId == listenerId) {
274             toDelete = key;
275             break;
276         }
277     }
278
279     if (typeof toDelete === 'undefined')
280         throw new WebAPIException(
281             WebAPIException.NOT_FOUND_ERR,
282             'The port of the target application is not found.'
283         );
284
285     listeners.splice(toDelete, 1);
286 };
287
288 function RemoteMessagePort(messagePortName, appId, isTrusted) {
289     Object.defineProperties(this, {
290         messagePortName: { value: messagePortName, writable: false },
291         appId: { value: appId, writable: false },
292         isTrusted: { value: !!isTrusted, writable: false }
293     });
294 }
295
296 function _isOctet(valArray) {
297     for (var i = 0; i < valArray.length; i++) {
298         if (valArray[i] < 0 || valArray[i] > 255) {
299             return false;
300         }
301     }
302     return true;
303 }
304
305 RemoteMessagePort.prototype.sendMessage = function() {
306     var args = validator_.validateArgs(arguments, [
307         { name: 'data', type: types_.ARRAY },
308         {
309             name: 'localMessagePort',
310             type: types_.PLATFORM_OBJECT,
311             optional: true,
312             nullable: true,
313             values: LocalMessagePort
314         }
315     ]);
316
317     var filtered_data = new Array(args.data.length);
318     var unique_data_key = {};
319     var data_length = args.data.length;
320     for (var i = 0; i < data_length; i++) {
321         if (!args.data[i].hasOwnProperty('key')) {
322             throw new WebAPIException(
323                 WebAPIException.INVALID_VALUES_ERR,
324                 'MessagePortDataItem should contain \'key\' property.'
325             );
326         }
327         var key = args.data[i].key;
328         if ('' === key) {
329             throw new WebAPIException(
330                 WebAPIException.INVALID_VALUES_ERR,
331                 'Property \'key\' should not be empty.'
332             );
333         }
334         if (true === unique_data_key[key]) {
335             throw new WebAPIException(
336                 WebAPIException.INVALID_VALUES_ERR,
337                 'Property \'key\' should not be duplicated.'
338             );
339         }
340         var value = args.data[i].value;
341         if (type_.isString(value)) {
342             filtered_data[i] = { key: key, value: value, valueType: 'stringValueType' };
343         } else if (type_.isArray(value)) {
344             var arrayMember = value[0];
345             if (type_.isString(arrayMember)) {
346                 for (var j = 1; j < value.length; j++) {
347                     if (!type_.isString(value[j])) {
348                         throw new WebAPIException(
349                             WebAPIException.INVALID_VALUES_ERR,
350                             'Invalid array value'
351                         );
352                     }
353                 }
354                 filtered_data[i] = {
355                     key: key,
356                     value: value,
357                     valueType: 'stringArrayValueType'
358                 };
359             } else if (!type_.isArray(arrayMember)) {
360                 if (!_isOctet(value)) {
361                     throw new WebAPIException(
362                         WebAPIException.INVALID_VALUES_ERR,
363                         'Data is not octet array'
364                     );
365                 }
366                 filtered_data[i] = {
367                     key: key,
368                     value: value,
369                     valueType: 'byteStreamValueType'
370                 };
371             } else {
372                 for (var j = 0; j < value.length; j++) {
373                     if (!_isOctet(value[j])) {
374                         throw new WebAPIException(
375                             WebAPIException.INVALID_VALUES_ERR,
376                             'Data is not octet array'
377                         );
378                     }
379                 }
380                 filtered_data[i] = {
381                     key: key,
382                     value: value,
383                     valueType: 'byteStreamArrayValueType'
384                 };
385             }
386         } else {
387             // convert any other value to string -> backward compatibility
388             filtered_data[i] = {
389                 key: key,
390                 value: converter_.toString(value),
391                 valueType: 'stringValueType'
392             };
393         }
394         unique_data_key[key] = true;
395     }
396
397     var nativeParam = {
398         appId: this.appId,
399         messagePortName: this.messagePortName,
400         data: filtered_data,
401         trusted: this.isTrusted,
402         local_port_id: args.localMessagePort
403             ? ports[args.localMessagePort.messagePortName]
404             : -1
405     };
406
407     var syncResult = callNative('RemoteMessagePort_sendMessage', nativeParam);
408 };
409
410 exports = new MessagePortManager();