1/*
2 * Copyright (c) 2022 Huawei Device Co., Ltd.
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
6 *
7 *     http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
14 */
15
16const distributedObject = requireInternal('data.distributedDataObject');
17const SESSION_ID = '__sessionId';
18const VERSION = '__version';
19const COMPLEX_TYPE = '[COMPLEX]';
20const STRING_TYPE = '[STRING]';
21const NULL_TYPE = '[NULL]';
22const ASSET_KEYS = ['status', 'name', 'uri', 'path', 'createTime', 'modifyTime', 'size'];
23const STATUS_INDEX = 0;
24const ASSET_KEY_SEPARATOR = '.';
25const JS_ERROR = 1;
26const SDK_VERSION_8 = 8;
27const SDK_VERSION_9 = 9;
28const SESSION_ID_REGEX = /^\w+$/;
29const SESSION_ID_MAX_LENGTH = 128;
30
31class Distributed {
32  constructor(obj) {
33    constructorMethod(this, obj);
34  }
35
36  setSessionId(sessionId) {
37    if (sessionId == null || sessionId === '') {
38      leaveSession(this.__sdkVersion, this.__proxy);
39      return false;
40    }
41    if (this.__proxy[SESSION_ID] === sessionId) {
42      console.info('same session has joined ' + sessionId);
43      return true;
44    }
45    leaveSession(this.__sdkVersion, this.__proxy);
46    let object = joinSession(this.__sdkVersion, this.__proxy, this.__objectId, sessionId);
47    if (object != null) {
48      this.__proxy = object;
49      return true;
50    }
51    return false;
52  }
53
54  on(type, callback) {
55    onWatch(this.__sdkVersion, type, this.__proxy, callback);
56    distributedObject.recordCallback(this.__sdkVersion, type, this.__objectId, callback);
57  }
58
59  off(type, callback) {
60    offWatch(this.__sdkVersion, type, this.__proxy, callback);
61    if (callback !== undefined || callback != null) {
62      distributedObject.deleteCallback(this.__sdkVersion, type, this.__objectId, callback);
63    } else {
64      distributedObject.deleteCallback(this.__sdkVersion, type, this.__objectId);
65    }
66  }
67
68  save(deviceId, callback) {
69    if (this.__proxy[SESSION_ID] == null || this.__proxy[SESSION_ID] === '') {
70      console.info('not join a session, can not do save');
71      return JS_ERROR;
72    }
73    return this.__proxy.save(deviceId, this[VERSION], callback);
74  }
75
76  revokeSave(callback) {
77    if (this.__proxy[SESSION_ID] == null || this.__proxy[SESSION_ID] === '') {
78      console.info('not join a session, can not do revoke save');
79      return JS_ERROR;
80    }
81    return this.__proxy.revokeSave(callback);
82  }
83
84  __proxy;
85  __objectId;
86  __version;
87  __sdkVersion = SDK_VERSION_8;
88}
89
90function constructorMethod(result, obj) {
91  result.__proxy = obj;
92  Object.keys(obj).forEach(key => {
93    Object.defineProperty(result, key, {
94      enumerable: true,
95      configurable: true,
96      get: function () {
97        return result.__proxy[key];
98      },
99      set: function (newValue) {
100        result[VERSION]++;
101        result.__proxy[key] = newValue;
102      }
103    });
104  });
105  Object.defineProperty(result, SESSION_ID, {
106    enumerable: true,
107    configurable: true,
108    get: function () {
109      return result.__proxy[SESSION_ID];
110    },
111    set: function (newValue) {
112      result.__proxy[SESSION_ID] = newValue;
113    }
114  });
115  result.__objectId = randomNum();
116  result[VERSION] = 0;
117  console.info('constructor success ');
118}
119
120function randomNum() {
121  return distributedObject.sequenceNum();
122}
123
124function newDistributed(obj) {
125  console.info('start newDistributed');
126  if (obj == null) {
127    console.error('object is null');
128    return null;
129  }
130  return new Distributed(obj);
131}
132
133function getObjectValue(object, key) {
134  console.info('start get ' + key);
135  let result = object.get(key);
136  if (typeof result === 'string') {
137    if (result.startsWith(STRING_TYPE)) {
138      result = result.substr(STRING_TYPE.length);
139    } else if (result.startsWith(COMPLEX_TYPE)) {
140      result = JSON.parse(result.substr(COMPLEX_TYPE.length));
141    } else if (result.startsWith(NULL_TYPE)) {
142      result = null;
143    } else {
144      console.error('error type');
145    }
146  }
147  console.info('get success');
148  return result;
149}
150
151function setObjectValue(object, key, newValue) {
152  console.info('start set ' + key);
153  if (typeof newValue === 'object') {
154    let value = COMPLEX_TYPE + JSON.stringify(newValue);
155    object.put(key, value);
156  } else if (typeof newValue === 'string') {
157    let value = STRING_TYPE + newValue;
158    object.put(key, value);
159  } else if (newValue == null) {
160    let value = NULL_TYPE;
161    object.put(key, value);
162  } else {
163    object.put(key, newValue);
164  }
165}
166
167function isAsset(obj) {
168  if (Object.prototype.toString.call(obj) !== '[object Object]') {
169    return false;
170  }
171  let length = Object.prototype.hasOwnProperty.call(obj, ASSET_KEYS[STATUS_INDEX]) ? ASSET_KEYS.length : ASSET_KEYS.length - 1;
172  if (Object.keys(obj).length !== length) {
173    return false;
174  }
175  if (Object.prototype.hasOwnProperty.call(obj, ASSET_KEYS[STATUS_INDEX]) &&
176    typeof obj[ASSET_KEYS[STATUS_INDEX]] !== 'number' && typeof obj[ASSET_KEYS[STATUS_INDEX]] !== 'undefined') {
177    return false;
178  }
179  for (const key of ASSET_KEYS.slice(1)) {
180    if (!Object.prototype.hasOwnProperty.call(obj, key) || typeof obj[key] !== 'string') {
181      return false;
182    }
183  }
184  return true;
185}
186
187function defineAsset(object, key, data) {
188  Object.defineProperty(object, key, {
189    enumerable: true,
190    configurable: true,
191    get: function () {
192      return getAssetValue(object, key);
193    },
194    set: function (newValue) {
195      setAssetValue(object, key, newValue);
196    }
197  });
198  let asset = object[key];
199  Object.keys(data).forEach(subKey => {
200    if (data[subKey] !== '') {
201      asset[subKey] = data[subKey];
202    }
203  });
204}
205
206function getAssetValue(object, key) {
207  let asset = {};
208  ASSET_KEYS.forEach(subKey => {
209    Object.defineProperty(asset, subKey, {
210      enumerable: true,
211      configurable: true,
212      get: function () {
213        return getObjectValue(object, key + ASSET_KEY_SEPARATOR + subKey);
214      },
215      set: function (newValue) {
216        setObjectValue(object, key + ASSET_KEY_SEPARATOR + subKey, newValue);
217      }
218    });
219  });
220  return asset;
221}
222
223function setAssetValue(object, key, newValue) {
224  if (!isAsset(newValue)) {
225    throw {
226      code: 401,
227      message: 'cannot set ' + key + ' by non Asset type data'
228    };
229  }
230  Object.keys(newValue).forEach(subKey => {
231    setObjectValue(object, key + ASSET_KEY_SEPARATOR + subKey, newValue[subKey]);
232  });
233}
234
235function joinSession(version, obj, objectId, sessionId, context) {
236  console.info('start joinSession ' + sessionId);
237  if (obj == null || sessionId == null || sessionId === '') {
238    console.error('object is null');
239    return null;
240  }
241
242  let object = null;
243  if (context !== undefined || context != null) {
244    object = distributedObject.createObjectSync(version, sessionId, objectId, context);
245  } else {
246    object = distributedObject.createObjectSync(version, sessionId, objectId);
247  }
248
249  if (object == null) {
250    console.error('create fail');
251    return null;
252  }
253  Object.keys(obj).forEach(key => {
254    console.info('start define ' + key);
255    if (isAsset(obj[key])) {
256      defineAsset(object, key, obj[key]);
257    } else {
258      Object.defineProperty(object, key, {
259        enumerable: true,
260        configurable: true,
261        get: function () {
262          return getObjectValue(object, key);
263        },
264        set: function (newValue) {
265          setObjectValue(object, key, newValue);
266        }
267      });
268      if (obj[key] !== undefined) {
269        object[key] = obj[key];
270      }
271    }
272  });
273
274  Object.defineProperty(object, SESSION_ID, {
275    value: sessionId,
276    configurable: true,
277  });
278  return object;
279}
280
281function leaveSession(version, obj) {
282  console.info('start leaveSession');
283  if (obj == null || obj[SESSION_ID] == null || obj[SESSION_ID] === '') {
284    console.warn('object is null');
285    return;
286  }
287  Object.keys(obj).forEach(key => {
288    Object.defineProperty(obj, key, {
289      value: obj[key],
290      configurable: true,
291      writable: true,
292      enumerable: true,
293    });
294    if (isAsset(obj[key])) {
295      Object.keys(obj[key]).forEach(subKey => {
296        Object.defineProperty(obj[key], subKey, {
297          value: obj[key][subKey],
298          configurable: true,
299          writable: true,
300          enumerable: true,
301        });
302      });
303    }
304  });
305  // disconnect,delete object
306  distributedObject.destroyObjectSync(version, obj);
307  delete obj[SESSION_ID];
308}
309
310function onWatch(version, type, obj, callback) {
311  console.info('start on ' + obj[SESSION_ID]);
312  if (obj[SESSION_ID] != null && obj[SESSION_ID] !== undefined && obj[SESSION_ID].length > 0) {
313    distributedObject.on(version, type, obj, callback);
314  }
315}
316
317function offWatch(version, type, obj, callback = undefined) {
318  console.info('start off ' + obj[SESSION_ID] + ' ' + callback);
319  if (obj[SESSION_ID] != null && obj[SESSION_ID] !== undefined && obj[SESSION_ID].length > 0) {
320    if (callback !== undefined || callback != null) {
321      distributedObject.off(version, type, obj, callback);
322    } else {
323      distributedObject.off(version, type, obj);
324    }
325  }
326}
327
328function newDistributedV9(context, obj) {
329  console.info('start newDistributed');
330  let checkparameter = function(parameter, type) {
331    throw {
332      code: 401,
333      message :"Parameter error. The type of '" + parameter + "' must be '" + type + "'."};
334  };
335  if (typeof context !== 'object') {
336    checkparameter('context', 'Context');
337  }
338  if (typeof obj !== 'object') {
339    checkparameter('source', 'object');
340  }
341  if (obj == null) {
342    console.error('object is null');
343    return null;
344  }
345  return new DistributedV9(obj, context);
346}
347
348class DistributedV9 {
349
350  constructor(obj, context) {
351    this.__context = context;
352    constructorMethod(this, obj);
353  }
354
355  setSessionId(sessionId, callback) {
356    if (typeof sessionId === 'function' || sessionId == null || sessionId === '') {
357      leaveSession(this.__sdkVersion, this.__proxy);
358      if (typeof sessionId === 'function') {
359        return sessionId(this.__proxy);
360      } else if (typeof callback === 'function') {
361        return callback(null, this.__proxy);
362      } else {
363        return Promise.resolve(null, this.__proxy);
364      }
365    }
366    if (this.__proxy[SESSION_ID] === sessionId) {
367      console.info('same session has joined ' + sessionId);
368      if (typeof callback === 'function') {
369        return callback(null, this.__proxy);
370      } else {
371        return Promise.resolve(null, this.__proxy);
372      }
373    }
374    leaveSession(this.__sdkVersion, this.__proxy);
375    if (sessionId.length > SESSION_ID_MAX_LENGTH || !SESSION_ID_REGEX.test(sessionId)) {
376      throw {
377        code: 401,
378        message: 'The sessionId allows only letters, digits, and underscores(_), and cannot exceed 128 in length.'
379      };
380    }
381    let object = joinSession(this.__sdkVersion, this.__proxy, this.__objectId, sessionId, this.__context);
382    if (object != null) {
383      this.__proxy = object;
384      if (typeof callback === 'function') {
385        return callback(null, this.__proxy);
386      } else {
387        return Promise.resolve(null, object);
388      }
389    } else {
390      if (typeof callback === 'function') {
391        return callback(null, null);
392      } else {
393        return Promise.reject(null, null);
394      }
395    }
396  }
397
398  on(type, callback) {
399    onWatch(this.__sdkVersion, type, this.__proxy, callback);
400    distributedObject.recordCallback(this.__sdkVersion, type, this.__objectId, callback);
401  }
402
403  off(type, callback) {
404    offWatch(this.__sdkVersion, type, this.__proxy, callback);
405    if (callback !== undefined || callback != null) {
406      distributedObject.deleteCallback(this.__sdkVersion, type, this.__objectId, callback);
407    } else {
408      distributedObject.deleteCallback(this.__sdkVersion, type, this.__objectId);
409    }
410  }
411
412  save(deviceId, callback) {
413    if (this.__proxy[SESSION_ID] == null || this.__proxy[SESSION_ID] === '') {
414      console.info('not join a session, can not do save');
415      return JS_ERROR;
416    }
417    return this.__proxy.save(deviceId, this[VERSION], callback);
418  }
419
420  revokeSave(callback) {
421    if (this.__proxy[SESSION_ID] == null || this.__proxy[SESSION_ID] === '') {
422      console.info('not join a session, can not do revoke save');
423      return JS_ERROR;
424    }
425    return this.__proxy.revokeSave(callback);
426  }
427
428  bindAssetStore(assetkey, bindInfo, callback) {
429    if (this.__proxy[SESSION_ID] == null || this.__proxy[SESSION_ID] === '') {
430      console.info('not join a session, can not do bindAssetStore');
431      return JS_ERROR;
432    }
433    return this.__proxy.bindAssetStore(assetkey, bindInfo, callback);
434  }
435
436  __context;
437  __proxy;
438  __objectId;
439  __version;
440  __sdkVersion = SDK_VERSION_9;
441}
442
443export default {
444  createDistributedObject: newDistributed,
445  create: newDistributedV9,
446  genSessionId: randomNum
447};
448