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 fs = requireInternal('file.fs'); 18const SESSION_ID = '__sessionId'; 19const VERSION = '__version'; 20const COMPLEX_TYPE = '[COMPLEX]'; 21const STRING_TYPE = '[STRING]'; 22const NULL_TYPE = '[NULL]'; 23const ASSET_KEYS = ['status', 'name', 'uri', 'path', 'createTime', 'modifyTime', 'size']; 24const STATUS_INDEX = 0; 25const ASSET_KEY_SEPARATOR = '.'; 26const JS_ERROR = 1; 27const SDK_VERSION_8 = 8; 28const SDK_VERSION_9 = 9; 29const SESSION_ID_REGEX = /^\w+$/; 30const SESSION_ID_MAX_LENGTH = 128; 31const ASSETS_MAX_NUMBER = 50; 32const HEAD_SIZE = 3; 33const END_SIZE = 3; 34const MIN_SIZE = HEAD_SIZE + END_SIZE + 3; 35const REPLACE_CHAIN = '***'; 36const DEFAULT_ANONYMOUS = '******'; 37 38class Distributed { 39 constructor(obj) { 40 constructorMethod(this, obj); 41 } 42 43 setSessionId(sessionId) { 44 if (sessionId == null || sessionId === '') { 45 leaveSession(this.__sdkVersion, this.__proxy); 46 return false; 47 } 48 if (this.__proxy[SESSION_ID] === sessionId) { 49 return true; 50 } 51 leaveSession(this.__sdkVersion, this.__proxy); 52 let object = joinSession(this.__sdkVersion, this.__proxy, this.__objectId, sessionId); 53 if (object != null) { 54 this.__proxy = object; 55 return true; 56 } 57 return false; 58 } 59 60 on(type, callback) { 61 onWatch(this.__sdkVersion, type, this.__proxy, callback); 62 distributedObject.recordCallback(this.__sdkVersion, type, this.__objectId, callback); 63 } 64 65 off(type, callback) { 66 offWatch(this.__sdkVersion, type, this.__proxy, callback); 67 if (callback !== undefined || callback != null) { 68 distributedObject.deleteCallback(this.__sdkVersion, type, this.__objectId, callback); 69 } else { 70 distributedObject.deleteCallback(this.__sdkVersion, type, this.__objectId); 71 } 72 } 73 74 save(deviceId, callback) { 75 if (this.__proxy[SESSION_ID] == null || this.__proxy[SESSION_ID] === '') { 76 console.info('not join a session, can not do save'); 77 return JS_ERROR; 78 } 79 return this.__proxy.save(deviceId, this[VERSION], callback); 80 } 81 82 revokeSave(callback) { 83 if (this.__proxy[SESSION_ID] == null || this.__proxy[SESSION_ID] === '') { 84 console.info('not join a session, can not do revoke save'); 85 return JS_ERROR; 86 } 87 return this.__proxy.revokeSave(callback); 88 } 89 90 __proxy; 91 __objectId; 92 __version; 93 __sdkVersion = SDK_VERSION_8; 94} 95 96function constructorMethod(result, obj) { 97 result.__proxy = obj; 98 Object.keys(obj).forEach(key => { 99 Object.defineProperty(result, key, { 100 enumerable: true, 101 configurable: true, 102 get: function () { 103 return result.__proxy[key]; 104 }, 105 set: function (newValue) { 106 result[VERSION]++; 107 result.__proxy[key] = newValue; 108 } 109 }); 110 }); 111 Object.defineProperty(result, SESSION_ID, { 112 enumerable: true, 113 configurable: true, 114 get: function () { 115 return result.__proxy[SESSION_ID]; 116 }, 117 set: function (newValue) { 118 result.__proxy[SESSION_ID] = newValue; 119 } 120 }); 121 result.__objectId = randomNum(); 122 result[VERSION] = 0; 123 console.info('constructor success '); 124} 125 126function randomNum() { 127 return distributedObject.sequenceNum(); 128} 129 130function newDistributed(obj) { 131 console.info('start newDistributed'); 132 if (obj == null) { 133 console.error('object is null'); 134 return null; 135 } 136 return new Distributed(obj); 137} 138 139function getObjectValue(object, key) { 140 console.info('start get ' + key); 141 let result = object.get(key); 142 if (typeof result === 'string') { 143 if (result.startsWith(STRING_TYPE)) { 144 result = result.substr(STRING_TYPE.length); 145 } else if (result.startsWith(COMPLEX_TYPE)) { 146 result = JSON.parse(result.substr(COMPLEX_TYPE.length)); 147 } else if (result.startsWith(NULL_TYPE)) { 148 result = null; 149 } else { 150 console.error('error type'); 151 } 152 } 153 console.info('get success'); 154 return result; 155} 156 157function setObjectValue(object, key, newValue) { 158 console.info('start set ' + key); 159 if (typeof newValue === 'object') { 160 let value = COMPLEX_TYPE + JSON.stringify(newValue); 161 object.put(key, value); 162 } else if (typeof newValue === 'string') { 163 let value = STRING_TYPE + newValue; 164 object.put(key, value); 165 } else if (newValue == null) { 166 let value = NULL_TYPE; 167 object.put(key, value); 168 } else { 169 object.put(key, newValue); 170 } 171} 172 173function isAsset(obj) { 174 if (Object.prototype.toString.call(obj) !== '[object Object]') { 175 return false; 176 } 177 let length = Object.prototype.hasOwnProperty.call(obj, ASSET_KEYS[STATUS_INDEX]) ? ASSET_KEYS.length : ASSET_KEYS.length - 1; 178 if (Object.keys(obj).length !== length) { 179 return false; 180 } 181 if (Object.prototype.hasOwnProperty.call(obj, ASSET_KEYS[STATUS_INDEX]) && 182 typeof obj[ASSET_KEYS[STATUS_INDEX]] !== 'number' && typeof obj[ASSET_KEYS[STATUS_INDEX]] !== 'undefined') { 183 return false; 184 } 185 for (const key of ASSET_KEYS.slice(1)) { 186 if (!Object.prototype.hasOwnProperty.call(obj, key) || typeof obj[key] !== 'string') { 187 return false; 188 } 189 } 190 return true; 191} 192 193function defineAsset(object, key, data) { 194 Object.defineProperty(object, key, { 195 enumerable: true, 196 configurable: true, 197 get: function () { 198 return getAssetValue(object, key); 199 }, 200 set: function (newValue) { 201 setAssetValue(object, key, newValue); 202 } 203 }); 204 let asset = object[key]; 205 Object.keys(data).forEach(subKey => { 206 if (data[subKey] !== '') { 207 asset[subKey] = data[subKey]; 208 } 209 }); 210} 211 212function getAssetValue(object, key) { 213 let asset = {}; 214 ASSET_KEYS.forEach(subKey => { 215 Object.defineProperty(asset, subKey, { 216 enumerable: true, 217 configurable: true, 218 get: function () { 219 return getObjectValue(object, key + ASSET_KEY_SEPARATOR + subKey); 220 }, 221 set: function (newValue) { 222 setObjectValue(object, key + ASSET_KEY_SEPARATOR + subKey, newValue); 223 } 224 }); 225 }); 226 return asset; 227} 228 229function setAssetValue(object, key, newValue) { 230 if (!isAsset(newValue)) { 231 throw { 232 code: 401, 233 message: 'cannot set ' + key + ' by non Asset type data' 234 }; 235 } 236 Object.keys(newValue).forEach(subKey => { 237 setObjectValue(object, key + ASSET_KEY_SEPARATOR + subKey, newValue[subKey]); 238 }); 239} 240 241function joinSession(version, obj, objectId, sessionId, context) { 242 if (obj == null || sessionId == null || sessionId === '') { 243 console.error('object is null'); 244 return null; 245 } 246 247 let object = null; 248 if (context !== undefined || context != null) { 249 object = distributedObject.createObjectSync(version, sessionId, objectId, context); 250 } else { 251 object = distributedObject.createObjectSync(version, sessionId, objectId); 252 } 253 254 if (object == null) { 255 console.error('create fail'); 256 return null; 257 } 258 Object.keys(obj).forEach(key => { 259 console.info('start define ' + key); 260 if (isAsset(obj[key])) { 261 defineAsset(object, key, obj[key]); 262 } else { 263 Object.defineProperty(object, key, { 264 enumerable: true, 265 configurable: true, 266 get: function () { 267 return getObjectValue(object, key); 268 }, 269 set: function (newValue) { 270 setObjectValue(object, key, newValue); 271 } 272 }); 273 if (obj[key] !== undefined) { 274 object[key] = obj[key]; 275 } 276 } 277 }); 278 279 Object.defineProperty(object, SESSION_ID, { 280 value: sessionId, 281 configurable: true, 282 }); 283 return object; 284} 285 286function leaveSession(version, obj) { 287 console.info('start leaveSession'); 288 if (obj == null || obj[SESSION_ID] == null || obj[SESSION_ID] === '') { 289 console.warn('object is null'); 290 return; 291 } 292 Object.keys(obj).forEach(key => { 293 Object.defineProperty(obj, key, { 294 value: obj[key], 295 configurable: true, 296 writable: true, 297 enumerable: true, 298 }); 299 if (isAsset(obj[key])) { 300 Object.keys(obj[key]).forEach(subKey => { 301 Object.defineProperty(obj[key], subKey, { 302 value: obj[key][subKey], 303 configurable: true, 304 writable: true, 305 enumerable: true, 306 }); 307 }); 308 } 309 }); 310 // disconnect,delete object 311 distributedObject.destroyObjectSync(version, obj); 312 delete obj[SESSION_ID]; 313} 314 315function toBeAnonymous(name) { 316 if (name == null || name === undefined || name === '') { 317 return ''; 318 } 319 if (name.length <= HEAD_SIZE) { 320 return DEFAULT_ANONYMOUS; 321 } 322 if (name.length < MIN_SIZE) { 323 return name.substring(0, HEAD_SIZE) + REPLACE_CHAIN; 324 } 325 return name.substring(0, HEAD_SIZE) + REPLACE_CHAIN + name.substring(name.length - END_SIZE); 326} 327 328function onWatch(version, type, obj, callback) { 329 console.info('start on ' + toBeAnonymous(obj[SESSION_ID])); 330 if (obj[SESSION_ID] != null && obj[SESSION_ID] !== undefined && obj[SESSION_ID].length > 0) { 331 distributedObject.on(version, type, obj, callback); 332 } 333} 334 335function offWatch(version, type, obj, callback = undefined) { 336 console.info('start off ' + toBeAnonymous(obj[SESSION_ID]) + ' ' + callback); 337 if (obj[SESSION_ID] != null && obj[SESSION_ID] !== undefined && obj[SESSION_ID].length > 0) { 338 if (callback !== undefined || callback != null) { 339 distributedObject.off(version, type, obj, callback); 340 } else { 341 distributedObject.off(version, type, obj); 342 } 343 } 344} 345 346function newDistributedV9(context, obj) { 347 console.info('start newDistributed'); 348 let checkparameter = function(parameter, type) { 349 throw { 350 code: 401, 351 message :"Parameter error. The type of '" + parameter + "' must be '" + type + "'."}; 352 }; 353 if (typeof context !== 'object') { 354 checkparameter('context', 'Context'); 355 } 356 if (typeof obj !== 'object') { 357 checkparameter('source', 'object'); 358 } 359 if (obj == null) { 360 console.error('object is null'); 361 return null; 362 } 363 return new DistributedV9(obj, context); 364} 365 366function appendPropertyToObj(result, obj) { 367 result.__proxy = Object.assign(result.__proxy, obj); 368 Object.keys(obj).forEach(key => { 369 Object.defineProperty(result, key, { 370 enumerable: true, 371 configurable: true, 372 get: function () { 373 return result.__proxy[key]; 374 }, 375 set: function (newValue) { 376 result.__proxy[key] = newValue; 377 } 378 }); 379 }); 380} 381 382function getDefaultAsset(uri, distributedDir) { 383 if (uri == null) { 384 throw { 385 code: 15400002, 386 message: 'The asset uri to be set is null.' 387 }; 388 } 389 const fileName = uri.substring(uri.lastIndexOf('/') + 1); 390 const filePath = distributedDir + '/' + fileName; 391 let stat; 392 try { 393 stat = fs.statSync(filePath); 394 return { 395 name: fileName, 396 uri: uri, 397 path: filePath, 398 createTime: stat.ctime.toString(), 399 modifyTime: stat.mtime.toString(), 400 size: stat.size.toString() 401 }; 402 } catch (error) { 403 console.error(error); 404 return { 405 name: '', 406 uri: '', 407 path: '', 408 createTime: 0, 409 modifyTime: 0, 410 size: 0 411 }; 412 } 413} 414 415class DistributedV9 { 416 417 constructor(obj, context) { 418 this.__context = context; 419 constructorMethod(this, obj); 420 } 421 422 setSessionId(sessionId, callback) { 423 if (typeof sessionId === 'function' || sessionId == null || sessionId === '') { 424 leaveSession(this.__sdkVersion, this.__proxy); 425 if (typeof sessionId === 'function') { 426 return sessionId(this.__proxy); 427 } else if (typeof callback === 'function') { 428 return callback(null, this.__proxy); 429 } else { 430 return Promise.resolve(null, this.__proxy); 431 } 432 } 433 if (this.__proxy[SESSION_ID] === sessionId) { 434 if (typeof callback === 'function') { 435 return callback(null, this.__proxy); 436 } else { 437 return Promise.resolve(null, this.__proxy); 438 } 439 } 440 leaveSession(this.__sdkVersion, this.__proxy); 441 if (sessionId.length > SESSION_ID_MAX_LENGTH || !SESSION_ID_REGEX.test(sessionId)) { 442 throw { 443 code: 401, 444 message: 'The sessionId allows only letters, digits, and underscores(_), and cannot exceed 128 in length.' 445 }; 446 } 447 let object = joinSession(this.__sdkVersion, this.__proxy, this.__objectId, sessionId, this.__context); 448 if (object != null) { 449 this.__proxy = object; 450 if (typeof callback === 'function') { 451 return callback(null, this.__proxy); 452 } else { 453 return Promise.resolve(null, object); 454 } 455 } else { 456 if (typeof callback === 'function') { 457 return callback(null, null); 458 } else { 459 return Promise.reject(null, null); 460 } 461 } 462 } 463 464 on(type, callback) { 465 onWatch(this.__sdkVersion, type, this.__proxy, callback); 466 distributedObject.recordCallback(this.__sdkVersion, type, this.__objectId, callback); 467 } 468 469 off(type, callback) { 470 offWatch(this.__sdkVersion, type, this.__proxy, callback); 471 if (callback !== undefined || callback != null) { 472 distributedObject.deleteCallback(this.__sdkVersion, type, this.__objectId, callback); 473 } else { 474 distributedObject.deleteCallback(this.__sdkVersion, type, this.__objectId); 475 } 476 } 477 478 save(deviceId, callback) { 479 if (this.__proxy[SESSION_ID] == null || this.__proxy[SESSION_ID] === '') { 480 console.info('not join a session, can not do save'); 481 return JS_ERROR; 482 } 483 return this.__proxy.save(deviceId, this[VERSION], callback); 484 } 485 486 revokeSave(callback) { 487 if (this.__proxy[SESSION_ID] == null || this.__proxy[SESSION_ID] === '') { 488 console.info('not join a session, can not do revoke save'); 489 return JS_ERROR; 490 } 491 return this.__proxy.revokeSave(callback); 492 } 493 494 bindAssetStore(assetkey, bindInfo, callback) { 495 if (this.__proxy[SESSION_ID] == null || this.__proxy[SESSION_ID] === '') { 496 console.info('not join a session, can not do bindAssetStore'); 497 return JS_ERROR; 498 } 499 return this.__proxy.bindAssetStore(assetkey, bindInfo, callback); 500 } 501 502 setAsset(assetKey, uri) { 503 if (this.__proxy[SESSION_ID] != null && this.__proxy[SESSION_ID] !== '') { 504 throw { 505 code: 15400003, 506 message: 'SessionId has been set, and asset cannot be set.' 507 }; 508 } 509 if (!assetKey || !uri) { 510 throw { 511 code: 15400002, 512 message: 'The property or uri of the asset is invalid.' 513 }; 514 } 515 516 let assetObj = {}; 517 const distributedDir = this.__context.distributedFilesDir; 518 const asset = getDefaultAsset(uri, distributedDir); 519 assetObj[assetKey] = [asset]; 520 assetObj[assetKey + '0'] = asset; 521 appendPropertyToObj(this, assetObj); 522 return Promise.resolve(); 523 } 524 525 setAssets(assetsKey, uris) { 526 if (this.__proxy[SESSION_ID] != null && this.__proxy[SESSION_ID] !== '') { 527 throw { 528 code: 15400003, 529 message: 'SessionId has been set, and assets cannot be set.' 530 }; 531 } 532 if (!assetsKey) { 533 throw { 534 code: 15400002, 535 message: 'The property of the assets is invalid.' 536 }; 537 } 538 if (!Array.isArray(uris) || uris.length <= 0 || uris.length > ASSETS_MAX_NUMBER) { 539 throw { 540 code: 15400002, 541 message: 'The uri array of the set assets is not an array or the length is invalid.' 542 }; 543 } 544 for (let index = 0; index < uris.length; index++) { 545 if (!uris[index]) { 546 throw { 547 code: 15400002, 548 message: 'Uri in assets array is invalid.' 549 }; 550 } 551 } 552 553 let assetObj = {}; 554 let assets = []; 555 const distributedDir = this.__context.distributedFilesDir; 556 for (let index = 0; index < uris.length; index++) { 557 const asset = getDefaultAsset(uris[index], distributedDir); 558 assets.push(asset); 559 assetObj[assetsKey + index] = asset; 560 } 561 assetObj[assetsKey] = assets; 562 appendPropertyToObj(this, assetObj); 563 return Promise.resolve(); 564 } 565 566 __context; 567 __proxy; 568 __objectId; 569 __version; 570 __sdkVersion = SDK_VERSION_9; 571} 572 573export default { 574 createDistributedObject: newDistributed, 575 create: newDistributedV9, 576 genSessionId: randomNum 577}; 578