roboto-js 1.6.9 → 1.6.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -39,7 +39,9 @@ var Roboto = exports["default"] = /*#__PURE__*/function () {
39
39
  function Roboto(_ref) {
40
40
  var host = _ref.host,
41
41
  accessKey = _ref.accessKey,
42
- localStorageAdaptor = _ref.localStorageAdaptor;
42
+ localStorageAdaptor = _ref.localStorageAdaptor,
43
+ _ref$disableWebSocket = _ref.disableWebSocket,
44
+ disableWebSocket = _ref$disableWebSocket === void 0 ? false : _ref$disableWebSocket;
43
45
  var proxyReq = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : null;
44
46
  _classCallCheck(this, Roboto);
45
47
  if (Roboto.instance && !proxyReq) {
@@ -83,6 +85,11 @@ var Roboto = exports["default"] = /*#__PURE__*/function () {
83
85
  Roboto.instance = this;
84
86
  }
85
87
  _createClass(Roboto, [{
88
+ key: "getVersion",
89
+ value: function getVersion() {
90
+ return '1.6.9';
91
+ }
92
+ }, {
86
93
  key: "setSiteEnv",
87
94
  value: function setSiteEnv(siteEnv) {
88
95
  this.api.setSiteEnv(siteEnv);
@@ -35,12 +35,14 @@ var RbtApi = exports["default"] = /*#__PURE__*/function () {
35
35
  _ref$localStorageAdap = _ref.localStorageAdaptor,
36
36
  localStorageAdaptor = _ref$localStorageAdap === void 0 ? null : _ref$localStorageAdap;
37
37
  _classCallCheck(this, RbtApi);
38
+ this.websocketClient = null;
38
39
  this.axios = _axios["default"].create({
39
40
  baseURL: baseUrl,
40
41
  headers: {
41
42
  'accesskey': accesskey
42
43
  }
43
44
  });
45
+ this.axios.__rbtApiInstance = this;
44
46
  if (localStorageAdaptor) {
45
47
  // must implement getItem, setItem interface
46
48
  this.localStorageAdaptor = localStorageAdaptor;
@@ -68,6 +70,25 @@ var RbtApi = exports["default"] = /*#__PURE__*/function () {
68
70
  this.initApiKey(apikey);
69
71
  }
70
72
  _createClass(RbtApi, [{
73
+ key: "getWebSocketClient",
74
+ value: function getWebSocketClient() {
75
+ if (this.websocketClient) return this.websocketClient;
76
+ var baseUrl = this.axios.defaults.baseURL;
77
+ var wsProtocol = baseUrl.startsWith('https') ? 'wss://' : 'ws://';
78
+ var wsUrl = baseUrl.replace(/^https?:\/\//, wsProtocol);
79
+ this.websocketClient = new WebSocket("".concat(wsUrl, "/realtime"));
80
+ this.websocketClient.onopen = function () {
81
+ console.log('[RbtApi] WebSocket connected.');
82
+ };
83
+ this.websocketClient.onclose = function () {
84
+ console.warn('[RbtApi] WebSocket closed.');
85
+ };
86
+ this.websocketClient.onerror = function (err) {
87
+ console.error('[RbtApi] WebSocket error:', err.message || err);
88
+ };
89
+ return this.websocketClient;
90
+ }
91
+ }, {
71
92
  key: "initAuthToken",
72
93
  value: function () {
73
94
  var _initAuthToken = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime().mark(function _callee(authtoken) {
@@ -792,28 +813,27 @@ var RbtApi = exports["default"] = /*#__PURE__*/function () {
792
813
  case 0:
793
814
  dataHash = _args17.length > 1 && _args17[1] !== undefined ? _args17[1] : {};
794
815
  _context17.prev = 1;
795
- debugger;
796
- _context17.next = 5;
816
+ _context17.next = 4;
797
817
  return this.axios.post('/object_service/createObject', [type, dataHash]);
798
- case 5:
818
+ case 4:
799
819
  response = _context17.sent;
800
820
  record = response.data;
801
821
  if (dataHash) {
802
822
  record.data = dataHash;
803
823
  }
804
- debugger;
805
824
  return _context17.abrupt("return", new _rbt_object["default"](record, this.axios, {
806
- isNew: true
825
+ isNew: true,
826
+ websocketClient: this.websocketClient
807
827
  }));
808
- case 12:
809
- _context17.prev = 12;
828
+ case 10:
829
+ _context17.prev = 10;
810
830
  _context17.t0 = _context17["catch"](1);
811
831
  return _context17.abrupt("return", this._handleError(_context17.t0));
812
- case 15:
832
+ case 13:
813
833
  case "end":
814
834
  return _context17.stop();
815
835
  }
816
- }, _callee17, this, [[1, 12]]);
836
+ }, _callee17, this, [[1, 10]]);
817
837
  }));
818
838
  function create(_x12) {
819
839
  return _create.apply(this, arguments);
@@ -848,48 +868,6 @@ var RbtApi = exports["default"] = /*#__PURE__*/function () {
848
868
  *
849
869
  * Note: A default orderBy is applied if none is provided, ordering items by 'timeCreated' in descending order.
850
870
  */
851
- // async query(type, params = {}) {
852
- // try {
853
- // console.log('RBTAPI.query INIT', type, params);
854
- // params.type = type;
855
- // // Default ordering and pagination
856
- // const defaultOrderBy = { orderBy: { column: 'timeCreated', direction: 'DESC' } };
857
- // const defaultLimit = { limit: { offset: 0, results: 50 } };
858
- //
859
- // // Merge defaults with provided params
860
- // const mergedParams = { ...defaultOrderBy, ...defaultLimit, ...params };
861
- // // Check cache for an existing request
862
- // const currentTime = Date.now();
863
- // const paramsKey = JSON.stringify(mergedParams);
864
- // const cacheEntry = this.requestCache[paramsKey];
865
- // if (cacheEntry && (currentTime - cacheEntry.time) < 10000) { // 10000 ms = 10 seconds
866
- // console.log('RBTAPI.query CACHED', type, paramsKey);
867
- // return cacheEntry.val;
868
- // }
869
- // // Create the response promise and store it in the cache
870
- // const responsePromise = this.axios.post('/object_service/queryObjects', [mergedParams]);
871
- // // Store the promise along with the current time in the cache
872
- // this.requestCache[paramsKey] = { val: responsePromise, time: currentTime };
873
- // // Await the response from the API
874
- // const response = await responsePromise;
875
- // if (response.data.ok === false) {
876
- // return this._handleError(response);
877
- // }
878
- // // Process items into RbtObject instances
879
- // if (Array.isArray(response.data.items)) {
880
- // response.data.items = response.data.items.map(record => {
881
- // return new RbtObject(record, this.axios, { isNew: true });
882
- // });
883
- // }
884
- // console.log('RBTAPI.query RESPONSE', type, paramsKey, response.data.items);
885
- // return response.data.items;
886
- // } catch (e) {
887
- // delete this.requestCache[paramsKey]; // Ensure cache cleanup on error
888
- // console.log('RBTAPI.query ERROR', paramsKey, e);
889
- // return this._handleError(e);
890
- //
891
- // }
892
- // }
893
871
  )
894
872
  }, {
895
873
  key: "query",
@@ -983,7 +961,9 @@ var RbtApi = exports["default"] = /*#__PURE__*/function () {
983
961
  if (Array.isArray(response.data.items)) {
984
962
  //console.log('RBTAPI.query RESPONSE PRE', response.data.items);
985
963
  response.data.items = response.data.items.map(function (record) {
986
- return new _rbt_object["default"](record, _this3.axios);
964
+ return new _rbt_object["default"](record, _this3.axios, {
965
+ websocketClient: _this3.websocketClient
966
+ });
987
967
  });
988
968
  }
989
969
 
@@ -6,6 +6,7 @@ Object.defineProperty(exports, "__esModule", {
6
6
  exports["default"] = void 0;
7
7
  var _lodash = _interopRequireDefault(require("lodash"));
8
8
  function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; }
9
+ function _createForOfIteratorHelper(o, allowArrayLike) { var it = typeof Symbol !== "undefined" && o[Symbol.iterator] || o["@@iterator"]; if (!it) { if (Array.isArray(o) || (it = _unsupportedIterableToArray(o)) || allowArrayLike && o && typeof o.length === "number") { if (it) o = it; var i = 0; var F = function F() {}; return { s: F, n: function n() { if (i >= o.length) return { done: true }; return { done: false, value: o[i++] }; }, e: function e(_e) { throw _e; }, f: F }; } throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } var normalCompletion = true, didErr = false, err; return { s: function s() { it = it.call(o); }, n: function n() { var step = it.next(); normalCompletion = step.done; return step; }, e: function e(_e2) { didErr = true; err = _e2; }, f: function f() { try { if (!normalCompletion && it["return"] != null) it["return"](); } finally { if (didErr) throw err; } } }; }
9
10
  function _regeneratorRuntime() { "use strict"; /*! regenerator-runtime -- Copyright (c) 2014-present, Facebook, Inc. -- license (MIT): https://github.com/facebook/regenerator/blob/main/LICENSE */ _regeneratorRuntime = function _regeneratorRuntime() { return e; }; var t, e = {}, r = Object.prototype, n = r.hasOwnProperty, o = Object.defineProperty || function (t, e, r) { t[e] = r.value; }, i = "function" == typeof Symbol ? Symbol : {}, a = i.iterator || "@@iterator", c = i.asyncIterator || "@@asyncIterator", u = i.toStringTag || "@@toStringTag"; function define(t, e, r) { return Object.defineProperty(t, e, { value: r, enumerable: !0, configurable: !0, writable: !0 }), t[e]; } try { define({}, ""); } catch (t) { define = function define(t, e, r) { return t[e] = r; }; } function wrap(t, e, r, n) { var i = e && e.prototype instanceof Generator ? e : Generator, a = Object.create(i.prototype), c = new Context(n || []); return o(a, "_invoke", { value: makeInvokeMethod(t, r, c) }), a; } function tryCatch(t, e, r) { try { return { type: "normal", arg: t.call(e, r) }; } catch (t) { return { type: "throw", arg: t }; } } e.wrap = wrap; var h = "suspendedStart", l = "suspendedYield", f = "executing", s = "completed", y = {}; function Generator() {} function GeneratorFunction() {} function GeneratorFunctionPrototype() {} var p = {}; define(p, a, function () { return this; }); var d = Object.getPrototypeOf, v = d && d(d(values([]))); v && v !== r && n.call(v, a) && (p = v); var g = GeneratorFunctionPrototype.prototype = Generator.prototype = Object.create(p); function defineIteratorMethods(t) { ["next", "throw", "return"].forEach(function (e) { define(t, e, function (t) { return this._invoke(e, t); }); }); } function AsyncIterator(t, e) { function invoke(r, o, i, a) { var c = tryCatch(t[r], t, o); if ("throw" !== c.type) { var u = c.arg, h = u.value; return h && "object" == _typeof(h) && n.call(h, "__await") ? e.resolve(h.__await).then(function (t) { invoke("next", t, i, a); }, function (t) { invoke("throw", t, i, a); }) : e.resolve(h).then(function (t) { u.value = t, i(u); }, function (t) { return invoke("throw", t, i, a); }); } a(c.arg); } var r; o(this, "_invoke", { value: function value(t, n) { function callInvokeWithMethodAndArg() { return new e(function (e, r) { invoke(t, n, e, r); }); } return r = r ? r.then(callInvokeWithMethodAndArg, callInvokeWithMethodAndArg) : callInvokeWithMethodAndArg(); } }); } function makeInvokeMethod(e, r, n) { var o = h; return function (i, a) { if (o === f) throw new Error("Generator is already running"); if (o === s) { if ("throw" === i) throw a; return { value: t, done: !0 }; } for (n.method = i, n.arg = a;;) { var c = n.delegate; if (c) { var u = maybeInvokeDelegate(c, n); if (u) { if (u === y) continue; return u; } } if ("next" === n.method) n.sent = n._sent = n.arg;else if ("throw" === n.method) { if (o === h) throw o = s, n.arg; n.dispatchException(n.arg); } else "return" === n.method && n.abrupt("return", n.arg); o = f; var p = tryCatch(e, r, n); if ("normal" === p.type) { if (o = n.done ? s : l, p.arg === y) continue; return { value: p.arg, done: n.done }; } "throw" === p.type && (o = s, n.method = "throw", n.arg = p.arg); } }; } function maybeInvokeDelegate(e, r) { var n = r.method, o = e.iterator[n]; if (o === t) return r.delegate = null, "throw" === n && e.iterator["return"] && (r.method = "return", r.arg = t, maybeInvokeDelegate(e, r), "throw" === r.method) || "return" !== n && (r.method = "throw", r.arg = new TypeError("The iterator does not provide a '" + n + "' method")), y; var i = tryCatch(o, e.iterator, r.arg); if ("throw" === i.type) return r.method = "throw", r.arg = i.arg, r.delegate = null, y; var a = i.arg; return a ? a.done ? (r[e.resultName] = a.value, r.next = e.nextLoc, "return" !== r.method && (r.method = "next", r.arg = t), r.delegate = null, y) : a : (r.method = "throw", r.arg = new TypeError("iterator result is not an object"), r.delegate = null, y); } function pushTryEntry(t) { var e = { tryLoc: t[0] }; 1 in t && (e.catchLoc = t[1]), 2 in t && (e.finallyLoc = t[2], e.afterLoc = t[3]), this.tryEntries.push(e); } function resetTryEntry(t) { var e = t.completion || {}; e.type = "normal", delete e.arg, t.completion = e; } function Context(t) { this.tryEntries = [{ tryLoc: "root" }], t.forEach(pushTryEntry, this), this.reset(!0); } function values(e) { if (e || "" === e) { var r = e[a]; if (r) return r.call(e); if ("function" == typeof e.next) return e; if (!isNaN(e.length)) { var o = -1, i = function next() { for (; ++o < e.length;) if (n.call(e, o)) return next.value = e[o], next.done = !1, next; return next.value = t, next.done = !0, next; }; return i.next = i; } } throw new TypeError(_typeof(e) + " is not iterable"); } return GeneratorFunction.prototype = GeneratorFunctionPrototype, o(g, "constructor", { value: GeneratorFunctionPrototype, configurable: !0 }), o(GeneratorFunctionPrototype, "constructor", { value: GeneratorFunction, configurable: !0 }), GeneratorFunction.displayName = define(GeneratorFunctionPrototype, u, "GeneratorFunction"), e.isGeneratorFunction = function (t) { var e = "function" == typeof t && t.constructor; return !!e && (e === GeneratorFunction || "GeneratorFunction" === (e.displayName || e.name)); }, e.mark = function (t) { return Object.setPrototypeOf ? Object.setPrototypeOf(t, GeneratorFunctionPrototype) : (t.__proto__ = GeneratorFunctionPrototype, define(t, u, "GeneratorFunction")), t.prototype = Object.create(g), t; }, e.awrap = function (t) { return { __await: t }; }, defineIteratorMethods(AsyncIterator.prototype), define(AsyncIterator.prototype, c, function () { return this; }), e.AsyncIterator = AsyncIterator, e.async = function (t, r, n, o, i) { void 0 === i && (i = Promise); var a = new AsyncIterator(wrap(t, r, n, o), i); return e.isGeneratorFunction(r) ? a : a.next().then(function (t) { return t.done ? t.value : a.next(); }); }, defineIteratorMethods(g), define(g, u, "Generator"), define(g, a, function () { return this; }), define(g, "toString", function () { return "[object Generator]"; }), e.keys = function (t) { var e = Object(t), r = []; for (var n in e) r.push(n); return r.reverse(), function next() { for (; r.length;) { var t = r.pop(); if (t in e) return next.value = t, next.done = !1, next; } return next.done = !0, next; }; }, e.values = values, Context.prototype = { constructor: Context, reset: function reset(e) { if (this.prev = 0, this.next = 0, this.sent = this._sent = t, this.done = !1, this.delegate = null, this.method = "next", this.arg = t, this.tryEntries.forEach(resetTryEntry), !e) for (var r in this) "t" === r.charAt(0) && n.call(this, r) && !isNaN(+r.slice(1)) && (this[r] = t); }, stop: function stop() { this.done = !0; var t = this.tryEntries[0].completion; if ("throw" === t.type) throw t.arg; return this.rval; }, dispatchException: function dispatchException(e) { if (this.done) throw e; var r = this; function handle(n, o) { return a.type = "throw", a.arg = e, r.next = n, o && (r.method = "next", r.arg = t), !!o; } for (var o = this.tryEntries.length - 1; o >= 0; --o) { var i = this.tryEntries[o], a = i.completion; if ("root" === i.tryLoc) return handle("end"); if (i.tryLoc <= this.prev) { var c = n.call(i, "catchLoc"), u = n.call(i, "finallyLoc"); if (c && u) { if (this.prev < i.catchLoc) return handle(i.catchLoc, !0); if (this.prev < i.finallyLoc) return handle(i.finallyLoc); } else if (c) { if (this.prev < i.catchLoc) return handle(i.catchLoc, !0); } else { if (!u) throw new Error("try statement without catch or finally"); if (this.prev < i.finallyLoc) return handle(i.finallyLoc); } } } }, abrupt: function abrupt(t, e) { for (var r = this.tryEntries.length - 1; r >= 0; --r) { var o = this.tryEntries[r]; if (o.tryLoc <= this.prev && n.call(o, "finallyLoc") && this.prev < o.finallyLoc) { var i = o; break; } } i && ("break" === t || "continue" === t) && i.tryLoc <= e && e <= i.finallyLoc && (i = null); var a = i ? i.completion : {}; return a.type = t, a.arg = e, i ? (this.method = "next", this.next = i.finallyLoc, y) : this.complete(a); }, complete: function complete(t, e) { if ("throw" === t.type) throw t.arg; return "break" === t.type || "continue" === t.type ? this.next = t.arg : "return" === t.type ? (this.rval = this.arg = t.arg, this.method = "return", this.next = "end") : "normal" === t.type && e && (this.next = e), y; }, finish: function finish(t) { for (var e = this.tryEntries.length - 1; e >= 0; --e) { var r = this.tryEntries[e]; if (r.finallyLoc === t) return this.complete(r.completion, r.afterLoc), resetTryEntry(r), y; } }, "catch": function _catch(t) { for (var e = this.tryEntries.length - 1; e >= 0; --e) { var r = this.tryEntries[e]; if (r.tryLoc === t) { var n = r.completion; if ("throw" === n.type) { var o = n.arg; resetTryEntry(r); } return o; } } throw new Error("illegal catch attempt"); }, delegateYield: function delegateYield(e, r, n) { return this.delegate = { iterator: values(e), resultName: r, nextLoc: n }, "next" === this.method && (this.arg = t), y; } }, e; }
10
11
  function asyncGeneratorStep(gen, resolve, reject, _next, _throw, key, arg) { try { var info = gen[key](arg); var value = info.value; } catch (error) { reject(error); return; } if (info.done) { resolve(value); } else { Promise.resolve(value).then(_next, _throw); } }
11
12
  function _asyncToGenerator(fn) { return function () { var self = this, args = arguments; return new Promise(function (resolve, reject) { var gen = fn.apply(self, args); function _next(value) { asyncGeneratorStep(gen, resolve, reject, _next, _throw, "next", value); } function _throw(err) { asyncGeneratorStep(gen, resolve, reject, _next, _throw, "throw", err); } _next(undefined); }); }; }
@@ -42,6 +43,15 @@ var RbtObject = exports["default"] = /*#__PURE__*/function () {
42
43
  } else {
43
44
  this._data = record.dataJson ? this._deepUnpackJson(record.dataJson) : {};
44
45
  }
46
+ if (options.websocketClient && this.id) {
47
+ this._realtime = true;
48
+ this._ws = options.websocketClient;
49
+ this._subscribeToRealtime(this._ws);
50
+ }
51
+ this._eventHandlers = {
52
+ change: [],
53
+ save: []
54
+ };
45
55
  }
46
56
  _createClass(RbtObject, [{
47
57
  key: "get",
@@ -116,6 +126,7 @@ var RbtObject = exports["default"] = /*#__PURE__*/function () {
116
126
  } else if (!_lodash["default"].isEqual(currentValue, value)) {
117
127
  _lodash["default"].set(this._data, path, value); // Set the value directly at the deep path
118
128
  this._addChange(path);
129
+ this._broadcastChange(path, value);
119
130
  }
120
131
  }
121
132
  }
@@ -229,38 +240,7 @@ var RbtObject = exports["default"] = /*#__PURE__*/function () {
229
240
  return _setRemove.apply(this, arguments);
230
241
  }
231
242
  return setRemove;
232
- }() // set(path, value, options = {}) {
233
- // const currentValue = _.get(this._data, path);
234
- //
235
- // // Check if merge is required
236
- // if (options.merge) {
237
- // // Merge the value if merge option is true
238
- // const mergedValue = _.merge({}, currentValue, value);
239
- // if (!_.isEqual(currentValue, mergedValue)) {
240
- // _.set(this._data, path, mergedValue);
241
- // this._addChange(path);
242
- // }
243
- // } else {
244
- // // Set the value directly if no merge option or merge option is false
245
- // if (!_.isEqual(currentValue, value)) {
246
- // _.set(this._data, path, value);
247
- // this._addChange(path);
248
- // }
249
- // }
250
- // }
251
- //
252
- // setData(newData) {
253
- // if (typeof newData !== 'object' || newData === null) {
254
- // throw new Error('setData expects an object');
255
- // }
256
- //
257
- // Object.keys(newData).forEach(key => {
258
- // if (!_.isEqual(_.get(this._data, key), newData[key])) {
259
- // _.set(this._data, key, newData[key]);
260
- // this._addChange(key);
261
- // }
262
- // });
263
- // }
243
+ }()
264
244
  }, {
265
245
  key: "getMetaData",
266
246
  value: function getMetaData() {
@@ -316,6 +296,10 @@ var RbtObject = exports["default"] = /*#__PURE__*/function () {
316
296
  delete clonedData.id;
317
297
  delete clonedData.id_revision;
318
298
  delete clonedData.rpcMeta;
299
+ if (clonedData.data) {
300
+ delete clonedData.data.id;
301
+ delete clonedData.data.id_revision;
302
+ }
319
303
 
320
304
  // Create a new instance of RbtObject with the cloned data
321
305
  var clonedObject = new RbtObject(clonedData, this._axios, {
@@ -459,6 +443,96 @@ var RbtObject = exports["default"] = /*#__PURE__*/function () {
459
443
  }
460
444
  return value;
461
445
  }
446
+
447
+ //
448
+ // Realtime WebSocket
449
+ //
450
+ //
451
+ }, {
452
+ key: "_initRealtime",
453
+ value: function _initRealtime() {
454
+ var _this$_axios;
455
+ if (this._realtime || !this._axios) return;
456
+
457
+ // Lazily pull WebSocket from parent API (injected via axios instance)
458
+ var api = (_this$_axios = this._axios) === null || _this$_axios === void 0 ? void 0 : _this$_axios.__rbtApiInstance;
459
+ if (!api || typeof api.getWebSocketClient !== 'function') return;
460
+ var ws = api.getWebSocketClient();
461
+ if (!ws || this._realtime) return;
462
+ this._ws = ws;
463
+ this._realtime = true;
464
+ this._subscribeToRealtime(ws);
465
+ }
466
+ }, {
467
+ key: "_subscribeToRealtime",
468
+ value: function _subscribeToRealtime(ws) {
469
+ var _this3 = this;
470
+ if (ws.readyState === 1) {
471
+ ws.send(JSON.stringify({
472
+ type: 'subscribe',
473
+ objectId: this.id
474
+ }));
475
+ } else {
476
+ ws.addEventListener('open', function () {
477
+ ws.send(JSON.stringify({
478
+ type: 'subscribe',
479
+ objectId: _this3.id
480
+ }));
481
+ });
482
+ }
483
+ ws.addEventListener('message', function (event) {
484
+ var msg = JSON.parse(event.data);
485
+ if (msg.objectId !== _this3.id) return;
486
+ if (msg.type === 'update') {
487
+ _lodash["default"].set(_this3._data, msg.delta.path, msg.delta.value);
488
+ _this3._trigger('change', msg.delta);
489
+ } else if (msg.type === 'save') {
490
+ _this3._trigger('save', msg.revision || {});
491
+ }
492
+ });
493
+ }
494
+ }, {
495
+ key: "onRealtimeChange",
496
+ value: function onRealtimeChange(cb) {
497
+ this._eventHandlers.change.push(cb);
498
+ this._initRealtime(); // lazy connect
499
+ }
500
+ }, {
501
+ key: "onRealtimeSave",
502
+ value: function onRealtimeSave(cb) {
503
+ this._eventHandlers.save.push(cb);
504
+ this._initRealtime(); // lazy connect
505
+ }
506
+ }, {
507
+ key: "_trigger",
508
+ value: function _trigger(type, data) {
509
+ var _iterator = _createForOfIteratorHelper(this._eventHandlers[type] || []),
510
+ _step;
511
+ try {
512
+ for (_iterator.s(); !(_step = _iterator.n()).done;) {
513
+ var fn = _step.value;
514
+ fn(data);
515
+ }
516
+ } catch (err) {
517
+ _iterator.e(err);
518
+ } finally {
519
+ _iterator.f();
520
+ }
521
+ }
522
+ }, {
523
+ key: "_broadcastChange",
524
+ value: function _broadcastChange(path, value) {
525
+ if (this._realtime && this._ws && this._ws.readyState === 1) {
526
+ this._ws.send(JSON.stringify({
527
+ type: 'update',
528
+ objectId: this.id,
529
+ delta: {
530
+ path: path,
531
+ value: value
532
+ }
533
+ }));
534
+ }
535
+ }
462
536
  }]);
463
537
  return RbtObject;
464
538
  }();
package/dist/esm/index.js CHANGED
@@ -6,10 +6,14 @@ export { RbtApi, RbtObject, RbtFile
6
6
  //Site
7
7
  };
8
8
  export default class Roboto {
9
+ getVersion() {
10
+ return '1.6.9';
11
+ }
9
12
  constructor({
10
13
  host,
11
14
  accessKey,
12
- localStorageAdaptor
15
+ localStorageAdaptor,
16
+ disableWebSocket = false
13
17
  }, proxyReq = null) {
14
18
  if (Roboto.instance && !proxyReq) {
15
19
  // if on client, there can only be one instance
@@ -13,12 +13,14 @@ export default class RbtApi {
13
13
  apikey = null,
14
14
  localStorageAdaptor = null
15
15
  }) {
16
+ this.websocketClient = null;
16
17
  this.axios = axios.create({
17
18
  baseURL: baseUrl,
18
19
  headers: {
19
20
  'accesskey': accesskey
20
21
  }
21
22
  });
23
+ this.axios.__rbtApiInstance = this;
22
24
  if (localStorageAdaptor) {
23
25
  // must implement getItem, setItem interface
24
26
  this.localStorageAdaptor = localStorageAdaptor;
@@ -39,6 +41,23 @@ export default class RbtApi {
39
41
  this.initAuthToken(authtoken);
40
42
  this.initApiKey(apikey);
41
43
  }
44
+ getWebSocketClient() {
45
+ if (this.websocketClient) return this.websocketClient;
46
+ const baseUrl = this.axios.defaults.baseURL;
47
+ const wsProtocol = baseUrl.startsWith('https') ? 'wss://' : 'ws://';
48
+ const wsUrl = baseUrl.replace(/^https?:\/\//, wsProtocol);
49
+ this.websocketClient = new WebSocket(`${wsUrl}/realtime`);
50
+ this.websocketClient.onopen = () => {
51
+ console.log('[RbtApi] WebSocket connected.');
52
+ };
53
+ this.websocketClient.onclose = () => {
54
+ console.warn('[RbtApi] WebSocket closed.');
55
+ };
56
+ this.websocketClient.onerror = err => {
57
+ console.error('[RbtApi] WebSocket error:', err.message || err);
58
+ };
59
+ return this.websocketClient;
60
+ }
42
61
  async initAuthToken(authtoken) {
43
62
  if (!authtoken && this.localStorageAdaptor) {
44
63
  authtoken = await this.localStorageAdaptor.getItem('authtoken');
@@ -373,15 +392,14 @@ export default class RbtApi {
373
392
  */
374
393
  async create(type, dataHash = {}) {
375
394
  try {
376
- debugger;
377
395
  const response = await this.axios.post('/object_service/createObject', [type, dataHash]);
378
396
  const record = response.data;
379
397
  if (dataHash) {
380
398
  record.data = dataHash;
381
399
  }
382
- debugger;
383
400
  return new RbtObject(record, this.axios, {
384
- isNew: true
401
+ isNew: true,
402
+ websocketClient: this.websocketClient
385
403
  });
386
404
  } catch (e) {
387
405
  return this._handleError(e);
@@ -416,61 +434,6 @@ export default class RbtApi {
416
434
  *
417
435
  * Note: A default orderBy is applied if none is provided, ordering items by 'timeCreated' in descending order.
418
436
  */
419
- // async query(type, params = {}) {
420
-
421
- // try {
422
-
423
- // console.log('RBTAPI.query INIT', type, params);
424
- // params.type = type;
425
-
426
- // // Default ordering and pagination
427
- // const defaultOrderBy = { orderBy: { column: 'timeCreated', direction: 'DESC' } };
428
- // const defaultLimit = { limit: { offset: 0, results: 50 } };
429
- //
430
- // // Merge defaults with provided params
431
- // const mergedParams = { ...defaultOrderBy, ...defaultLimit, ...params };
432
-
433
- // // Check cache for an existing request
434
- // const currentTime = Date.now();
435
- // const paramsKey = JSON.stringify(mergedParams);
436
- // const cacheEntry = this.requestCache[paramsKey];
437
- // if (cacheEntry && (currentTime - cacheEntry.time) < 10000) { // 10000 ms = 10 seconds
438
- // console.log('RBTAPI.query CACHED', type, paramsKey);
439
- // return cacheEntry.val;
440
- // }
441
-
442
- // // Create the response promise and store it in the cache
443
- // const responsePromise = this.axios.post('/object_service/queryObjects', [mergedParams]);
444
-
445
- // // Store the promise along with the current time in the cache
446
- // this.requestCache[paramsKey] = { val: responsePromise, time: currentTime };
447
-
448
- // // Await the response from the API
449
- // const response = await responsePromise;
450
-
451
- // if (response.data.ok === false) {
452
- // return this._handleError(response);
453
- // }
454
-
455
- // // Process items into RbtObject instances
456
- // if (Array.isArray(response.data.items)) {
457
- // response.data.items = response.data.items.map(record => {
458
- // return new RbtObject(record, this.axios, { isNew: true });
459
- // });
460
- // }
461
-
462
- // console.log('RBTAPI.query RESPONSE', type, paramsKey, response.data.items);
463
- // return response.data.items;
464
-
465
- // } catch (e) {
466
-
467
- // delete this.requestCache[paramsKey]; // Ensure cache cleanup on error
468
- // console.log('RBTAPI.query ERROR', paramsKey, e);
469
- // return this._handleError(e);
470
- //
471
- // }
472
-
473
- // }
474
437
 
475
438
  async query(type, params = {}) {
476
439
  let paramsKey;
@@ -540,7 +503,9 @@ export default class RbtApi {
540
503
  if (Array.isArray(response.data.items)) {
541
504
  //console.log('RBTAPI.query RESPONSE PRE', response.data.items);
542
505
  response.data.items = response.data.items.map(record => {
543
- return new RbtObject(record, this.axios);
506
+ return new RbtObject(record, this.axios, {
507
+ websocketClient: this.websocketClient
508
+ });
544
509
  });
545
510
  }
546
511
 
@@ -15,6 +15,15 @@ export default class RbtObject {
15
15
  } else {
16
16
  this._data = record.dataJson ? this._deepUnpackJson(record.dataJson) : {};
17
17
  }
18
+ if (options.websocketClient && this.id) {
19
+ this._realtime = true;
20
+ this._ws = options.websocketClient;
21
+ this._subscribeToRealtime(this._ws);
22
+ }
23
+ this._eventHandlers = {
24
+ change: [],
25
+ save: []
26
+ };
18
27
  }
19
28
  get(path) {
20
29
  return _.get(this._data, path);
@@ -80,6 +89,7 @@ export default class RbtObject {
80
89
  } else if (!_.isEqual(currentValue, value)) {
81
90
  _.set(this._data, path, value); // Set the value directly at the deep path
82
91
  this._addChange(path);
92
+ this._broadcastChange(path, value);
83
93
  }
84
94
  }
85
95
  }
@@ -145,40 +155,6 @@ export default class RbtObject {
145
155
  const filteredValues = existingValue.filter(item => !valuesToRemove.includes(item));
146
156
  this.set(key, filteredValues); // Set the updated array back to the user data
147
157
  }
148
-
149
- // set(path, value, options = {}) {
150
- // const currentValue = _.get(this._data, path);
151
- //
152
- // // Check if merge is required
153
- // if (options.merge) {
154
- // // Merge the value if merge option is true
155
- // const mergedValue = _.merge({}, currentValue, value);
156
- // if (!_.isEqual(currentValue, mergedValue)) {
157
- // _.set(this._data, path, mergedValue);
158
- // this._addChange(path);
159
- // }
160
- // } else {
161
- // // Set the value directly if no merge option or merge option is false
162
- // if (!_.isEqual(currentValue, value)) {
163
- // _.set(this._data, path, value);
164
- // this._addChange(path);
165
- // }
166
- // }
167
- // }
168
- //
169
- // setData(newData) {
170
- // if (typeof newData !== 'object' || newData === null) {
171
- // throw new Error('setData expects an object');
172
- // }
173
- //
174
- // Object.keys(newData).forEach(key => {
175
- // if (!_.isEqual(_.get(this._data, key), newData[key])) {
176
- // _.set(this._data, key, newData[key]);
177
- // this._addChange(key);
178
- // }
179
- // });
180
- // }
181
-
182
158
  getMetaData() {
183
159
  let meta = {
184
160
  timeCreated: this._internalData.timeCreated,
@@ -228,6 +204,10 @@ export default class RbtObject {
228
204
  delete clonedData.id;
229
205
  delete clonedData.id_revision;
230
206
  delete clonedData.rpcMeta;
207
+ if (clonedData.data) {
208
+ delete clonedData.data.id;
209
+ delete clonedData.data.id_revision;
210
+ }
231
211
 
232
212
  // Create a new instance of RbtObject with the cloned data
233
213
  const clonedObject = new RbtObject(clonedData, this._axios, {
@@ -313,4 +293,71 @@ export default class RbtObject {
313
293
  }
314
294
  return value;
315
295
  }
296
+
297
+ //
298
+ // Realtime WebSocket
299
+ //
300
+ //
301
+ _initRealtime() {
302
+ if (this._realtime || !this._axios) return;
303
+
304
+ // Lazily pull WebSocket from parent API (injected via axios instance)
305
+ const api = this._axios?.__rbtApiInstance;
306
+ if (!api || typeof api.getWebSocketClient !== 'function') return;
307
+ const ws = api.getWebSocketClient();
308
+ if (!ws || this._realtime) return;
309
+ this._ws = ws;
310
+ this._realtime = true;
311
+ this._subscribeToRealtime(ws);
312
+ }
313
+ _subscribeToRealtime(ws) {
314
+ if (ws.readyState === 1) {
315
+ ws.send(JSON.stringify({
316
+ type: 'subscribe',
317
+ objectId: this.id
318
+ }));
319
+ } else {
320
+ ws.addEventListener('open', () => {
321
+ ws.send(JSON.stringify({
322
+ type: 'subscribe',
323
+ objectId: this.id
324
+ }));
325
+ });
326
+ }
327
+ ws.addEventListener('message', event => {
328
+ const msg = JSON.parse(event.data);
329
+ if (msg.objectId !== this.id) return;
330
+ if (msg.type === 'update') {
331
+ _.set(this._data, msg.delta.path, msg.delta.value);
332
+ this._trigger('change', msg.delta);
333
+ } else if (msg.type === 'save') {
334
+ this._trigger('save', msg.revision || {});
335
+ }
336
+ });
337
+ }
338
+ onRealtimeChange(cb) {
339
+ this._eventHandlers.change.push(cb);
340
+ this._initRealtime(); // lazy connect
341
+ }
342
+ onRealtimeSave(cb) {
343
+ this._eventHandlers.save.push(cb);
344
+ this._initRealtime(); // lazy connect
345
+ }
346
+ _trigger(type, data) {
347
+ for (const fn of this._eventHandlers[type] || []) {
348
+ fn(data);
349
+ }
350
+ }
351
+ _broadcastChange(path, value) {
352
+ if (this._realtime && this._ws && this._ws.readyState === 1) {
353
+ this._ws.send(JSON.stringify({
354
+ type: 'update',
355
+ objectId: this.id,
356
+ delta: {
357
+ path,
358
+ value
359
+ }
360
+ }));
361
+ }
362
+ }
316
363
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "roboto-js",
3
- "version": "1.6.9",
3
+ "version": "1.6.11",
4
4
  "type": "module",
5
5
  "description": "",
6
6
  "main": "dist/cjs/index.cjs",
package/src/index.js CHANGED
@@ -13,7 +13,11 @@ export {
13
13
 
14
14
  export default class Roboto{
15
15
 
16
- constructor({ host, accessKey, localStorageAdaptor }, proxyReq = null) {
16
+ getVersion(){
17
+ return '1.6.9';
18
+ }
19
+
20
+ constructor({ host, accessKey, localStorageAdaptor, disableWebSocket = false }, proxyReq = null) {
17
21
 
18
22
  if (Roboto.instance && !proxyReq) {
19
23
  // if on client, there can only be one instance
package/src/rbt_api.js CHANGED
@@ -10,12 +10,15 @@ export default class RbtApi {
10
10
 
11
11
  constructor({ baseUrl, accesskey, authtoken=null, apikey=null, localStorageAdaptor=null }) {
12
12
 
13
+ this.websocketClient = null;
14
+
13
15
  this.axios = axios.create({
14
16
  baseURL: baseUrl,
15
17
  headers: {
16
18
  'accesskey': accesskey
17
19
  }
18
20
  });
21
+ this.axios.__rbtApiInstance = this;
19
22
 
20
23
  if(localStorageAdaptor){
21
24
  // must implement getItem, setItem interface
@@ -37,9 +40,33 @@ export default class RbtApi {
37
40
  // Use the storageAdaptor to get the authToken, if available
38
41
  this.initAuthToken(authtoken);
39
42
  this.initApiKey(apikey);
43
+
40
44
 
41
45
  }
42
46
 
47
+ getWebSocketClient() {
48
+ if (this.websocketClient) return this.websocketClient;
49
+
50
+ const baseUrl = this.axios.defaults.baseURL;
51
+ const wsProtocol = baseUrl.startsWith('https') ? 'wss://' : 'ws://';
52
+ const wsUrl = baseUrl.replace(/^https?:\/\//, wsProtocol);
53
+ this.websocketClient = new WebSocket(`${wsUrl}/realtime`);
54
+
55
+ this.websocketClient.onopen = () => {
56
+ console.log('[RbtApi] WebSocket connected.');
57
+ };
58
+
59
+ this.websocketClient.onclose = () => {
60
+ console.warn('[RbtApi] WebSocket closed.');
61
+ };
62
+
63
+ this.websocketClient.onerror = (err) => {
64
+ console.error('[RbtApi] WebSocket error:', err.message || err);
65
+ };
66
+
67
+ return this.websocketClient;
68
+ }
69
+
43
70
  async initAuthToken(authtoken) {
44
71
 
45
72
  if(!authtoken && this.localStorageAdaptor){
@@ -470,15 +497,13 @@ export default class RbtApi {
470
497
  */
471
498
  async create(type, dataHash={}) {
472
499
  try {
473
- debugger;
474
500
  const response = await this.axios.post('/object_service/createObject', [type, dataHash]);
475
501
  const record = response.data;
476
502
 
477
503
  if(dataHash){
478
504
  record.data = dataHash;
479
505
  }
480
- debugger;
481
- return new RbtObject(record, this.axios, { isNew: true });
506
+ return new RbtObject(record, this.axios, { isNew: true, websocketClient: this.websocketClient });
482
507
  } catch (e) {
483
508
  return this._handleError(e);
484
509
  }
@@ -512,62 +537,6 @@ export default class RbtApi {
512
537
  *
513
538
  * Note: A default orderBy is applied if none is provided, ordering items by 'timeCreated' in descending order.
514
539
  */
515
- // async query(type, params = {}) {
516
-
517
- // try {
518
-
519
- // console.log('RBTAPI.query INIT', type, params);
520
- // params.type = type;
521
-
522
- // // Default ordering and pagination
523
- // const defaultOrderBy = { orderBy: { column: 'timeCreated', direction: 'DESC' } };
524
- // const defaultLimit = { limit: { offset: 0, results: 50 } };
525
- //
526
- // // Merge defaults with provided params
527
- // const mergedParams = { ...defaultOrderBy, ...defaultLimit, ...params };
528
-
529
- // // Check cache for an existing request
530
- // const currentTime = Date.now();
531
- // const paramsKey = JSON.stringify(mergedParams);
532
- // const cacheEntry = this.requestCache[paramsKey];
533
- // if (cacheEntry && (currentTime - cacheEntry.time) < 10000) { // 10000 ms = 10 seconds
534
- // console.log('RBTAPI.query CACHED', type, paramsKey);
535
- // return cacheEntry.val;
536
- // }
537
-
538
- // // Create the response promise and store it in the cache
539
- // const responsePromise = this.axios.post('/object_service/queryObjects', [mergedParams]);
540
-
541
- // // Store the promise along with the current time in the cache
542
- // this.requestCache[paramsKey] = { val: responsePromise, time: currentTime };
543
-
544
- // // Await the response from the API
545
- // const response = await responsePromise;
546
-
547
- // if (response.data.ok === false) {
548
- // return this._handleError(response);
549
- // }
550
-
551
- // // Process items into RbtObject instances
552
- // if (Array.isArray(response.data.items)) {
553
- // response.data.items = response.data.items.map(record => {
554
- // return new RbtObject(record, this.axios, { isNew: true });
555
- // });
556
- // }
557
-
558
- // console.log('RBTAPI.query RESPONSE', type, paramsKey, response.data.items);
559
- // return response.data.items;
560
-
561
- // } catch (e) {
562
-
563
- // delete this.requestCache[paramsKey]; // Ensure cache cleanup on error
564
- // console.log('RBTAPI.query ERROR', paramsKey, e);
565
- // return this._handleError(e);
566
- //
567
- // }
568
-
569
- // }
570
-
571
540
 
572
541
  async query(type, params = {}) {
573
542
 
@@ -623,7 +592,7 @@ export default class RbtApi {
623
592
  if (Array.isArray(response.data.items)) {
624
593
  //console.log('RBTAPI.query RESPONSE PRE', response.data.items);
625
594
  response.data.items = response.data.items.map(record => {
626
- return new RbtObject(record, this.axios);
595
+ return new RbtObject(record, this.axios, { websocketClient: this.websocketClient });
627
596
  });
628
597
  }
629
598
 
package/src/rbt_object.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import _ from 'lodash';
2
2
 
3
- export default class RbtObject {
3
+ export default class RbtObject{
4
4
 
5
5
  constructor(record, axiosInstance, options = {}) {
6
6
  this._axios = axiosInstance;
@@ -19,6 +19,15 @@ export default class RbtObject {
19
19
  this._data = record.dataJson ? this._deepUnpackJson(record.dataJson) : {};
20
20
  }
21
21
 
22
+ if (options.websocketClient && this.id) {
23
+ this._realtime = true;
24
+ this._ws = options.websocketClient;
25
+ this._subscribeToRealtime(this._ws);
26
+ }
27
+ this._eventHandlers = {
28
+ change: [],
29
+ save: []
30
+ };
22
31
  }
23
32
 
24
33
  get(path) {
@@ -86,6 +95,7 @@ export default class RbtObject {
86
95
  } else if (!_.isEqual(currentValue, value)) {
87
96
  _.set(this._data, path, value); // Set the value directly at the deep path
88
97
  this._addChange(path);
98
+ this._broadcastChange(path, value);
89
99
  }
90
100
  }
91
101
  }
@@ -161,41 +171,6 @@ export default class RbtObject {
161
171
 
162
172
  }
163
173
 
164
-
165
-
166
- // set(path, value, options = {}) {
167
- // const currentValue = _.get(this._data, path);
168
- //
169
- // // Check if merge is required
170
- // if (options.merge) {
171
- // // Merge the value if merge option is true
172
- // const mergedValue = _.merge({}, currentValue, value);
173
- // if (!_.isEqual(currentValue, mergedValue)) {
174
- // _.set(this._data, path, mergedValue);
175
- // this._addChange(path);
176
- // }
177
- // } else {
178
- // // Set the value directly if no merge option or merge option is false
179
- // if (!_.isEqual(currentValue, value)) {
180
- // _.set(this._data, path, value);
181
- // this._addChange(path);
182
- // }
183
- // }
184
- // }
185
- //
186
- // setData(newData) {
187
- // if (typeof newData !== 'object' || newData === null) {
188
- // throw new Error('setData expects an object');
189
- // }
190
- //
191
- // Object.keys(newData).forEach(key => {
192
- // if (!_.isEqual(_.get(this._data, key), newData[key])) {
193
- // _.set(this._data, key, newData[key]);
194
- // this._addChange(key);
195
- // }
196
- // });
197
- // }
198
-
199
174
  getMetaData() {
200
175
 
201
176
  let meta = {
@@ -251,6 +226,10 @@ export default class RbtObject {
251
226
  delete clonedData.id;
252
227
  delete clonedData.id_revision;
253
228
  delete clonedData.rpcMeta;
229
+ if(clonedData.data){
230
+ delete clonedData.data.id;
231
+ delete clonedData.data.id_revision;
232
+ }
254
233
 
255
234
  // Create a new instance of RbtObject with the cloned data
256
235
  const clonedObject = new RbtObject(clonedData, this._axios, { isNew: true });
@@ -355,6 +334,72 @@ export default class RbtObject {
355
334
 
356
335
 
357
336
 
337
+ //
338
+ // Realtime WebSocket
339
+ //
340
+ //
341
+ _initRealtime() {
342
+ if (this._realtime || !this._axios) return;
343
+
344
+ // Lazily pull WebSocket from parent API (injected via axios instance)
345
+ const api = this._axios?.__rbtApiInstance;
346
+ if (!api || typeof api.getWebSocketClient !== 'function') return;
347
+
348
+ const ws = api.getWebSocketClient();
349
+ if (!ws || this._realtime) return;
350
+
351
+ this._ws = ws;
352
+ this._realtime = true;
353
+ this._subscribeToRealtime(ws);
354
+ }
355
+
356
+ _subscribeToRealtime(ws) {
357
+ if (ws.readyState === 1) {
358
+ ws.send(JSON.stringify({ type: 'subscribe', objectId: this.id }));
359
+ } else {
360
+ ws.addEventListener('open', () => {
361
+ ws.send(JSON.stringify({ type: 'subscribe', objectId: this.id }));
362
+ });
363
+ }
364
+
365
+ ws.addEventListener('message', (event) => {
366
+ const msg = JSON.parse(event.data);
367
+ if (msg.objectId !== this.id) return;
368
+
369
+ if (msg.type === 'update') {
370
+ _.set(this._data, msg.delta.path, msg.delta.value);
371
+ this._trigger('change', msg.delta);
372
+ } else if (msg.type === 'save') {
373
+ this._trigger('save', msg.revision || {});
374
+ }
375
+ });
376
+ }
377
+
378
+ onRealtimeChange(cb) {
379
+ this._eventHandlers.change.push(cb);
380
+ this._initRealtime(); // lazy connect
381
+ }
382
+
383
+ onRealtimeSave(cb) {
384
+ this._eventHandlers.save.push(cb);
385
+ this._initRealtime(); // lazy connect
386
+ }
387
+
388
+ _trigger(type, data) {
389
+ for (const fn of this._eventHandlers[type] || []) {
390
+ fn(data);
391
+ }
392
+ }
393
+
394
+ _broadcastChange(path, value) {
395
+ if (this._realtime && this._ws && this._ws.readyState === 1) {
396
+ this._ws.send(JSON.stringify({
397
+ type: 'update',
398
+ objectId: this.id,
399
+ delta: { path, value }
400
+ }));
401
+ }
402
+ }
358
403
 
359
404
 
360
405
  }