roboto-js 1.9.4 → 1.9.7
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.
- package/.last-build +1 -1
- package/README.md +580 -0
- package/dist/cjs/cookie_storage_adaptor.cjs +22 -7
- package/dist/cjs/index.cjs +251 -81
- package/dist/cjs/rbt_api.cjs +529 -298
- package/dist/cjs/version.cjs +2 -2
- package/dist/cookie_storage_adaptor.js +12 -26
- package/dist/esm/cookie_storage_adaptor.js +22 -7
- package/dist/esm/index.js +251 -81
- package/dist/esm/rbt_api.js +529 -298
- package/dist/esm/version.js +2 -2
- package/dist/index.js +118 -3
- package/dist/rbt_object.js +8 -4
- package/dist/version.js +2 -2
- package/package.json +1 -1
- package/src/cookie_storage_adaptor.js +14 -15
- package/src/index.js +123 -2
- package/src/rbt_api.js +164 -0
- package/src/version.js +2 -2
package/dist/esm/version.js
CHANGED
package/dist/index.js
CHANGED
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
|
|
2
|
+
function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
|
|
3
|
+
function _defineProperty(e, r, t) { return (r = _toPropertyKey(r)) in e ? Object.defineProperty(e, r, { value: t, enumerable: !0, configurable: !0, writable: !0 }) : e[r] = t, e; }
|
|
1
4
|
function _typeof(o) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) { return typeof o; } : function (o) { return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o; }, _typeof(o); }
|
|
2
5
|
function _regenerator() { /*! regenerator-runtime -- Copyright (c) 2014-present, Facebook, Inc. -- license (MIT): https://github.com/babel/babel/blob/main/packages/babel-helpers/LICENSE */ var e, t, r = "function" == typeof Symbol ? Symbol : {}, n = r.iterator || "@@iterator", o = r.toStringTag || "@@toStringTag"; function i(r, n, o, i) { var c = n && n.prototype instanceof Generator ? n : Generator, u = Object.create(c.prototype); return _regeneratorDefine2(u, "_invoke", function (r, n, o) { var i, c, u, f = 0, p = o || [], y = !1, G = { p: 0, n: 0, v: e, a: d, f: d.bind(e, 4), d: function d(t, r) { return i = t, c = 0, u = e, G.n = r, a; } }; function d(r, n) { for (c = r, u = n, t = 0; !y && f && !o && t < p.length; t++) { var o, i = p[t], d = G.p, l = i[2]; r > 3 ? (o = l === n) && (u = i[(c = i[4]) ? 5 : (c = 3, 3)], i[4] = i[5] = e) : i[0] <= d && ((o = r < 2 && d < i[1]) ? (c = 0, G.v = n, G.n = i[1]) : d < l && (o = r < 3 || i[0] > n || n > l) && (i[4] = r, i[5] = n, G.n = l, c = 0)); } if (o || r > 1) return a; throw y = !0, n; } return function (o, p, l) { if (f > 1) throw TypeError("Generator is already running"); for (y && 1 === p && d(p, l), c = p, u = l; (t = c < 2 ? e : u) || !y;) { i || (c ? c < 3 ? (c > 1 && (G.n = -1), d(c, u)) : G.n = u : G.v = u); try { if (f = 2, i) { if (c || (o = "next"), t = i[o]) { if (!(t = t.call(i, u))) throw TypeError("iterator result is not an object"); if (!t.done) return t; u = t.value, c < 2 && (c = 0); } else 1 === c && (t = i["return"]) && t.call(i), c < 2 && (u = TypeError("The iterator does not provide a '" + o + "' method"), c = 1); i = e; } else if ((t = (y = G.n < 0) ? u : r.call(n, G)) !== a) break; } catch (t) { i = e, c = 1, u = t; } finally { f = 1; } } return { value: t, done: y }; }; }(r, o, i), !0), u; } var a = {}; function Generator() {} function GeneratorFunction() {} function GeneratorFunctionPrototype() {} t = Object.getPrototypeOf; var c = [][n] ? t(t([][n]())) : (_regeneratorDefine2(t = {}, n, function () { return this; }), t), u = GeneratorFunctionPrototype.prototype = Generator.prototype = Object.create(c); function f(e) { return Object.setPrototypeOf ? Object.setPrototypeOf(e, GeneratorFunctionPrototype) : (e.__proto__ = GeneratorFunctionPrototype, _regeneratorDefine2(e, o, "GeneratorFunction")), e.prototype = Object.create(u), e; } return GeneratorFunction.prototype = GeneratorFunctionPrototype, _regeneratorDefine2(u, "constructor", GeneratorFunctionPrototype), _regeneratorDefine2(GeneratorFunctionPrototype, "constructor", GeneratorFunction), GeneratorFunction.displayName = "GeneratorFunction", _regeneratorDefine2(GeneratorFunctionPrototype, o, "GeneratorFunction"), _regeneratorDefine2(u), _regeneratorDefine2(u, o, "Generator"), _regeneratorDefine2(u, n, function () { return this; }), _regeneratorDefine2(u, "toString", function () { return "[object Generator]"; }), (_regenerator = function _regenerator() { return { w: i, m: f }; })(); }
|
|
3
6
|
function _regeneratorDefine2(e, r, n, t) { var i = Object.defineProperty; try { i({}, "", {}); } catch (e) { i = 0; } _regeneratorDefine2 = function _regeneratorDefine(e, r, n, t) { function o(r, n) { _regeneratorDefine2(e, r, function (e) { return this._invoke(r, n, e); }); } r ? i ? i(e, r, { value: n, enumerable: !t, configurable: !t, writable: !t }) : e[r] = n : (o("next", 0), o("throw", 1), o("return", 2)); }, _regeneratorDefine2(e, r, n, t); }
|
|
@@ -232,6 +235,53 @@ var Roboto = /*#__PURE__*/function () {
|
|
|
232
235
|
value: function recordToInstance(recordHash) {
|
|
233
236
|
return new RbtObject(recordHash, this.api.axios);
|
|
234
237
|
}
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* Wraps raw API response data as RbtObject(s)
|
|
241
|
+
* Use this when receiving raw JSON from custom API endpoints (e.g., /api/tasks/*)
|
|
242
|
+
* that return database objects instead of using roboto's built-in query methods.
|
|
243
|
+
*
|
|
244
|
+
* @param {Object|Array|null} data - Raw response data (single object or array)
|
|
245
|
+
* @returns {RbtObject|Array<RbtObject>|null} - Wrapped RbtObject(s) or original value if null/already wrapped
|
|
246
|
+
*
|
|
247
|
+
* @example
|
|
248
|
+
* // Wrapping a single object
|
|
249
|
+
* const response = await fetch('/api/tasks/boards/123');
|
|
250
|
+
* const data = await response.json();
|
|
251
|
+
* const board = roboto.wrapAsRbtObjects(data.board);
|
|
252
|
+
* board.set('title', 'New Title'); // Now you can use RbtObject methods
|
|
253
|
+
*
|
|
254
|
+
* @example
|
|
255
|
+
* // Wrapping an array
|
|
256
|
+
* const response = await fetch('/api/tasks/boards');
|
|
257
|
+
* const data = await response.json();
|
|
258
|
+
* const boards = roboto.wrapAsRbtObjects(data.boards);
|
|
259
|
+
* boards.forEach(board => console.log(board.get('title')));
|
|
260
|
+
*/
|
|
261
|
+
}, {
|
|
262
|
+
key: "wrapAsRbtObjects",
|
|
263
|
+
value: function wrapAsRbtObjects(data) {
|
|
264
|
+
var _this2 = this;
|
|
265
|
+
if (!data) return data;
|
|
266
|
+
|
|
267
|
+
// If it's already an RbtObject, return as-is (idempotent)
|
|
268
|
+
if (typeof data.get === 'function') {
|
|
269
|
+
return data;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
// If it's an array, wrap each item
|
|
273
|
+
if (Array.isArray(data)) {
|
|
274
|
+
return data.map(function (item) {
|
|
275
|
+
if (typeof (item === null || item === void 0 ? void 0 : item.get) === 'function') {
|
|
276
|
+
return item;
|
|
277
|
+
}
|
|
278
|
+
return new RbtObject(item, _this2.api.axios);
|
|
279
|
+
});
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
// Single object - wrap it
|
|
283
|
+
return new RbtObject(data, this.api.axios);
|
|
284
|
+
}
|
|
235
285
|
}, {
|
|
236
286
|
key: "_stripHttpsForDomains",
|
|
237
287
|
value: function _stripHttpsForDomains(baseUrl, domains) {
|
|
@@ -585,17 +635,77 @@ var Roboto = /*#__PURE__*/function () {
|
|
|
585
635
|
return _pollTaskProgress.apply(this, arguments);
|
|
586
636
|
}
|
|
587
637
|
return pollTaskProgress;
|
|
588
|
-
}()
|
|
638
|
+
}()
|
|
639
|
+
/**
|
|
640
|
+
* Helper to detect if an object looks like a doctree object
|
|
641
|
+
* @private
|
|
642
|
+
*/
|
|
643
|
+
}, {
|
|
644
|
+
key: "_isDoctreeObject",
|
|
645
|
+
value: function _isDoctreeObject(obj) {
|
|
646
|
+
if (!obj || _typeof(obj) !== 'object') return false;
|
|
647
|
+
// Check for standard doctree fields
|
|
648
|
+
return obj.hasOwnProperty('id') && obj.hasOwnProperty('type') && obj.hasOwnProperty('timeCreated');
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
/**
|
|
652
|
+
* Auto-wraps response data if it contains doctree objects
|
|
653
|
+
* @private
|
|
654
|
+
*/
|
|
655
|
+
}, {
|
|
656
|
+
key: "_autoWrapResponse",
|
|
657
|
+
value: function _autoWrapResponse(data) {
|
|
658
|
+
if (!data) return data;
|
|
659
|
+
|
|
660
|
+
// If response has an 'items' array (common pattern), wrap those
|
|
661
|
+
if (data.items && Array.isArray(data.items)) {
|
|
662
|
+
if (data.items.length > 0 && this._isDoctreeObject(data.items[0])) {
|
|
663
|
+
return _objectSpread(_objectSpread({}, data), {}, {
|
|
664
|
+
items: this.wrapAsRbtObjects(data.items)
|
|
665
|
+
});
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
// If response has a single object property that looks like doctree
|
|
670
|
+
// Check common property names: board, ticket, task, note, etc.
|
|
671
|
+
for (var _i = 0, _Object$keys = Object.keys(data); _i < _Object$keys.length; _i++) {
|
|
672
|
+
var key = _Object$keys[_i];
|
|
673
|
+
if (this._isDoctreeObject(data[key])) {
|
|
674
|
+
return _objectSpread(_objectSpread({}, data), {}, _defineProperty({}, key, this.wrapAsRbtObjects(data[key])));
|
|
675
|
+
}
|
|
676
|
+
// Check if it's an array of doctree objects
|
|
677
|
+
if (Array.isArray(data[key]) && data[key].length > 0 && this._isDoctreeObject(data[key][0])) {
|
|
678
|
+
return _objectSpread(_objectSpread({}, data), {}, _defineProperty({}, key, this.wrapAsRbtObjects(data[key])));
|
|
679
|
+
}
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
// If the root data itself is a doctree object or array of them
|
|
683
|
+
if (this._isDoctreeObject(data)) {
|
|
684
|
+
return this.wrapAsRbtObjects(data);
|
|
685
|
+
}
|
|
686
|
+
if (Array.isArray(data) && data.length > 0 && this._isDoctreeObject(data[0])) {
|
|
687
|
+
return this.wrapAsRbtObjects(data);
|
|
688
|
+
}
|
|
689
|
+
return data;
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
//
|
|
589
693
|
// Get/Post to endpoint with Roboto authtoken
|
|
694
|
+
// Automatically wraps doctree objects in responses as RbtObjects
|
|
590
695
|
//
|
|
591
696
|
}, {
|
|
592
697
|
key: "get",
|
|
593
698
|
value: function () {
|
|
594
699
|
var _get = _asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee18(endpoint, params) {
|
|
700
|
+
var result;
|
|
595
701
|
return _regenerator().w(function (_context18) {
|
|
596
702
|
while (1) switch (_context18.n) {
|
|
597
703
|
case 0:
|
|
598
|
-
|
|
704
|
+
_context18.n = 1;
|
|
705
|
+
return this.api.get(endpoint, params);
|
|
706
|
+
case 1:
|
|
707
|
+
result = _context18.v;
|
|
708
|
+
return _context18.a(2, this._autoWrapResponse(result));
|
|
599
709
|
}
|
|
600
710
|
}, _callee18, this);
|
|
601
711
|
}));
|
|
@@ -608,10 +718,15 @@ var Roboto = /*#__PURE__*/function () {
|
|
|
608
718
|
key: "post",
|
|
609
719
|
value: function () {
|
|
610
720
|
var _post = _asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee19(endpoint, data) {
|
|
721
|
+
var result;
|
|
611
722
|
return _regenerator().w(function (_context19) {
|
|
612
723
|
while (1) switch (_context19.n) {
|
|
613
724
|
case 0:
|
|
614
|
-
|
|
725
|
+
_context19.n = 1;
|
|
726
|
+
return this.api.post(endpoint, data);
|
|
727
|
+
case 1:
|
|
728
|
+
result = _context19.v;
|
|
729
|
+
return _context19.a(2, this._autoWrapResponse(result));
|
|
615
730
|
}
|
|
616
731
|
}, _callee19, this);
|
|
617
732
|
}));
|
package/dist/rbt_object.js
CHANGED
|
@@ -473,6 +473,7 @@ var RbtObject = /*#__PURE__*/function () {
|
|
|
473
473
|
replace,
|
|
474
474
|
_options$save,
|
|
475
475
|
save,
|
|
476
|
+
currentIac,
|
|
476
477
|
iac,
|
|
477
478
|
grantType,
|
|
478
479
|
existingUsers,
|
|
@@ -497,8 +498,9 @@ var RbtObject = /*#__PURE__*/function () {
|
|
|
497
498
|
}
|
|
498
499
|
throw new Error('groupIds must be an array');
|
|
499
500
|
case 2:
|
|
500
|
-
// Get current IAC settings
|
|
501
|
-
|
|
501
|
+
// Get current IAC settings and create a deep clone to ensure change detection
|
|
502
|
+
currentIac = this.get('iac') || {};
|
|
503
|
+
iac = _.cloneDeep(currentIac); // Determine which grant type to update (read or write)
|
|
502
504
|
grantType = write ? 'writeGrants' : 'readGrants'; // Initialize grants if they don't exist
|
|
503
505
|
if (!iac[grantType]) {
|
|
504
506
|
iac[grantType] = {};
|
|
@@ -733,6 +735,7 @@ var RbtObject = /*#__PURE__*/function () {
|
|
|
733
735
|
write,
|
|
734
736
|
_options$save3,
|
|
735
737
|
save,
|
|
738
|
+
currentIac,
|
|
736
739
|
iac,
|
|
737
740
|
grantType,
|
|
738
741
|
_args7 = arguments;
|
|
@@ -753,8 +756,9 @@ var RbtObject = /*#__PURE__*/function () {
|
|
|
753
756
|
}
|
|
754
757
|
throw new Error('groupIds must be an array');
|
|
755
758
|
case 2:
|
|
756
|
-
// Get current IAC settings
|
|
757
|
-
|
|
759
|
+
// Get current IAC settings and create a deep clone to ensure change detection
|
|
760
|
+
currentIac = this.get('iac') || {};
|
|
761
|
+
iac = _.cloneDeep(currentIac); // Determine which grant type to update (read or write)
|
|
758
762
|
grantType = write ? 'writeGrants' : 'readGrants'; // Initialize grants if they don't exist
|
|
759
763
|
if (!iac[grantType]) {
|
|
760
764
|
iac[grantType] = {};
|
package/dist/version.js
CHANGED
package/package.json
CHANGED
|
@@ -25,7 +25,7 @@ export default class CookieStorageAdaptor {
|
|
|
25
25
|
|
|
26
26
|
...options
|
|
27
27
|
}
|
|
28
|
-
|
|
28
|
+
/*
|
|
29
29
|
console.log('[CookieStorageAdaptor] Initialized with options:', {
|
|
30
30
|
secure: this.options.secure,
|
|
31
31
|
sameSite: this.options.sameSite,
|
|
@@ -34,7 +34,7 @@ export default class CookieStorageAdaptor {
|
|
|
34
34
|
domain: this.options.domain,
|
|
35
35
|
prefix: this.options.prefix,
|
|
36
36
|
serverAccessKeys: this.options.serverAccessKeys
|
|
37
|
-
})
|
|
37
|
+
})*/
|
|
38
38
|
}
|
|
39
39
|
|
|
40
40
|
/**
|
|
@@ -42,7 +42,6 @@ export default class CookieStorageAdaptor {
|
|
|
42
42
|
*/
|
|
43
43
|
async getItem(key) {
|
|
44
44
|
if (typeof document === 'undefined') {
|
|
45
|
-
console.log(`[CookieStorageAdaptor] getItem(${key}): document undefined, returning null`)
|
|
46
45
|
return null
|
|
47
46
|
}
|
|
48
47
|
|
|
@@ -53,28 +52,28 @@ export default class CookieStorageAdaptor {
|
|
|
53
52
|
const decodedCookie = decodeURIComponent(document.cookie)
|
|
54
53
|
const cookies = decodedCookie.split(';')
|
|
55
54
|
|
|
56
|
-
console.log(`[CookieStorageAdaptor] getItem(${key}): looking for cookie "${cookieName}" ${usePrefix ? '(prefixed)' : '(server-accessible)'}`)
|
|
57
|
-
console.log(`[CookieStorageAdaptor] Available cookies:`, cookies.map(c => c.trim().split('=')[0]).join(', '))
|
|
55
|
+
//console.log(`[CookieStorageAdaptor] getItem(${key}): looking for cookie "${cookieName}" ${usePrefix ? '(prefixed)' : '(server-accessible)'}`)
|
|
56
|
+
//console.log(`[CookieStorageAdaptor] Available cookies:`, cookies.map(c => c.trim().split('=')[0]).join(', '))
|
|
58
57
|
|
|
59
58
|
for (let cookie of cookies) {
|
|
60
59
|
cookie = cookie.trim()
|
|
61
60
|
if (cookie.indexOf(name) === 0) {
|
|
62
61
|
const rawValue = cookie.substring(name.length, cookie.length)
|
|
63
|
-
console.log(`[CookieStorageAdaptor] Found cookie "${cookieName}" with raw value:`, rawValue)
|
|
62
|
+
//console.log(`[CookieStorageAdaptor] Found cookie "${cookieName}" with raw value:`, rawValue)
|
|
64
63
|
|
|
65
64
|
// Handle JSON values (like rbtUser)
|
|
66
65
|
try {
|
|
67
66
|
const parsedValue = JSON.parse(rawValue)
|
|
68
|
-
console.log(`[CookieStorageAdaptor] getItem(${key}): returning parsed JSON:`, parsedValue)
|
|
67
|
+
//console.log(`[CookieStorageAdaptor] getItem(${key}): returning parsed JSON:`, parsedValue)
|
|
69
68
|
return parsedValue
|
|
70
69
|
} catch {
|
|
71
|
-
console.log(`[CookieStorageAdaptor] getItem(${key}): returning string value:`, rawValue)
|
|
70
|
+
//console.log(`[CookieStorageAdaptor] getItem(${key}): returning string value:`, rawValue)
|
|
72
71
|
return rawValue
|
|
73
72
|
}
|
|
74
73
|
}
|
|
75
74
|
}
|
|
76
75
|
|
|
77
|
-
|
|
76
|
+
//.log(`[CookieStorageAdaptor] getItem(${key}): cookie "${cookieName}" not found, returning null`)
|
|
78
77
|
return null
|
|
79
78
|
}
|
|
80
79
|
|
|
@@ -94,9 +93,9 @@ export default class CookieStorageAdaptor {
|
|
|
94
93
|
// Stringify objects/arrays like localStorage does
|
|
95
94
|
const cookieValue = typeof value === 'object' ? JSON.stringify(value) : String(value)
|
|
96
95
|
|
|
97
|
-
console.log(`[CookieStorageAdaptor] setItem(${key}): storing as "${cookieName}" ${usePrefix ? '(prefixed)' : '(server-accessible)'}`)
|
|
98
|
-
console.log(`[CookieStorageAdaptor] Original value:`, value)
|
|
99
|
-
console.log(`[CookieStorageAdaptor] Cookie value:`, cookieValue)
|
|
96
|
+
//console.log(`[CookieStorageAdaptor] setItem(${key}): storing as "${cookieName}" ${usePrefix ? '(prefixed)' : '(server-accessible)'}`)
|
|
97
|
+
//console.log(`[CookieStorageAdaptor] Original value:`, value)
|
|
98
|
+
//console.log(`[CookieStorageAdaptor] Cookie value:`, cookieValue)
|
|
100
99
|
|
|
101
100
|
// Build cookie string with security options
|
|
102
101
|
const secureFlag = this.options.secure ? '; Secure' : ''
|
|
@@ -105,7 +104,7 @@ export default class CookieStorageAdaptor {
|
|
|
105
104
|
|
|
106
105
|
const cookieString = `${cookieName}=${encodeURIComponent(cookieValue)}; path=${this.options.path}; max-age=${this.options.maxAge}; SameSite=${this.options.sameSite}${secureFlag}${domainFlag}${httpOnlyFlag}`
|
|
107
106
|
|
|
108
|
-
console.log(`[CookieStorageAdaptor] Full cookie string:`, cookieString)
|
|
107
|
+
//console.log(`[CookieStorageAdaptor] Full cookie string:`, cookieString)
|
|
109
108
|
|
|
110
109
|
document.cookie = cookieString
|
|
111
110
|
|
|
@@ -131,7 +130,7 @@ export default class CookieStorageAdaptor {
|
|
|
131
130
|
const usePrefix = !this.options.serverAccessKeys.includes(key)
|
|
132
131
|
const cookieName = usePrefix ? this.options.prefix + key : key
|
|
133
132
|
|
|
134
|
-
console.log(`[CookieStorageAdaptor] removeItem(${key}): removing cookie "${cookieName}" ${usePrefix ? '(prefixed)' : '(server-accessible)'}`)
|
|
133
|
+
//console.log(`[CookieStorageAdaptor] removeItem(${key}): removing cookie "${cookieName}" ${usePrefix ? '(prefixed)' : '(server-accessible)'}`)
|
|
135
134
|
|
|
136
135
|
// Check if cookie exists before removal
|
|
137
136
|
const existingValue = await this.getItem(key)
|
|
@@ -145,7 +144,7 @@ export default class CookieStorageAdaptor {
|
|
|
145
144
|
const domainFlag = this.options.domain ? `; Domain=${this.options.domain}` : ''
|
|
146
145
|
|
|
147
146
|
const removalString = `${cookieName}=; path=${this.options.path}; expires=Thu, 01 Jan 1970 00:00:00 GMT; SameSite=${this.options.sameSite}${secureFlag}${domainFlag}`
|
|
148
|
-
console.log(`[CookieStorageAdaptor] Removal cookie string:`, removalString)
|
|
147
|
+
//console.log(`[CookieStorageAdaptor] Removal cookie string:`, removalString)
|
|
149
148
|
|
|
150
149
|
document.cookie = removalString
|
|
151
150
|
|
package/src/index.js
CHANGED
|
@@ -205,6 +205,50 @@ export default class Roboto{
|
|
|
205
205
|
|
|
206
206
|
}
|
|
207
207
|
|
|
208
|
+
/**
|
|
209
|
+
* Wraps raw API response data as RbtObject(s)
|
|
210
|
+
* Use this when receiving raw JSON from custom API endpoints (e.g., /api/tasks/*)
|
|
211
|
+
* that return database objects instead of using roboto's built-in query methods.
|
|
212
|
+
*
|
|
213
|
+
* @param {Object|Array|null} data - Raw response data (single object or array)
|
|
214
|
+
* @returns {RbtObject|Array<RbtObject>|null} - Wrapped RbtObject(s) or original value if null/already wrapped
|
|
215
|
+
*
|
|
216
|
+
* @example
|
|
217
|
+
* // Wrapping a single object
|
|
218
|
+
* const response = await fetch('/api/tasks/boards/123');
|
|
219
|
+
* const data = await response.json();
|
|
220
|
+
* const board = roboto.wrapAsRbtObjects(data.board);
|
|
221
|
+
* board.set('title', 'New Title'); // Now you can use RbtObject methods
|
|
222
|
+
*
|
|
223
|
+
* @example
|
|
224
|
+
* // Wrapping an array
|
|
225
|
+
* const response = await fetch('/api/tasks/boards');
|
|
226
|
+
* const data = await response.json();
|
|
227
|
+
* const boards = roboto.wrapAsRbtObjects(data.boards);
|
|
228
|
+
* boards.forEach(board => console.log(board.get('title')));
|
|
229
|
+
*/
|
|
230
|
+
wrapAsRbtObjects(data) {
|
|
231
|
+
if (!data) return data;
|
|
232
|
+
|
|
233
|
+
// If it's already an RbtObject, return as-is (idempotent)
|
|
234
|
+
if (typeof data.get === 'function') {
|
|
235
|
+
return data;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// If it's an array, wrap each item
|
|
239
|
+
if (Array.isArray(data)) {
|
|
240
|
+
return data.map(item => {
|
|
241
|
+
if (typeof item?.get === 'function') {
|
|
242
|
+
return item;
|
|
243
|
+
}
|
|
244
|
+
return new RbtObject(item, this.api.axios);
|
|
245
|
+
});
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// Single object - wrap it
|
|
249
|
+
return new RbtObject(data, this.api.axios);
|
|
250
|
+
}
|
|
251
|
+
|
|
208
252
|
_stripHttpsForDomains(baseUrl, domains) {
|
|
209
253
|
return baseUrl.replace(new RegExp(`^https://(${domains.map(domain => domain.replace(/\./g, '\\.')).join('|')})`), 'http://$1');
|
|
210
254
|
}
|
|
@@ -255,6 +299,22 @@ export default class Roboto{
|
|
|
255
299
|
return this.api.confirmUserEmail(params);
|
|
256
300
|
}
|
|
257
301
|
|
|
302
|
+
//
|
|
303
|
+
// Organization management
|
|
304
|
+
//
|
|
305
|
+
async loadCurrentOrganization(forceReload = false){
|
|
306
|
+
return this.api.loadCurrentOrganization(forceReload);
|
|
307
|
+
}
|
|
308
|
+
async switchOrganization(orgId){
|
|
309
|
+
return this.api.switchOrganization(orgId);
|
|
310
|
+
}
|
|
311
|
+
getCurrentOrganization(){
|
|
312
|
+
return this.api.getCurrentOrganization();
|
|
313
|
+
}
|
|
314
|
+
get currentOrganization(){
|
|
315
|
+
return this.api.currentOrganization;
|
|
316
|
+
}
|
|
317
|
+
|
|
258
318
|
//
|
|
259
319
|
// create and upload files
|
|
260
320
|
//
|
|
@@ -306,15 +366,76 @@ export default class Roboto{
|
|
|
306
366
|
return this.api.pollTaskProgress(params);
|
|
307
367
|
}
|
|
308
368
|
|
|
369
|
+
/**
|
|
370
|
+
* Helper to detect if an object looks like a doctree object
|
|
371
|
+
* @private
|
|
372
|
+
*/
|
|
373
|
+
_isDoctreeObject(obj) {
|
|
374
|
+
if (!obj || typeof obj !== 'object') return false;
|
|
375
|
+
// Check for standard doctree fields
|
|
376
|
+
return obj.hasOwnProperty('id') &&
|
|
377
|
+
obj.hasOwnProperty('type') &&
|
|
378
|
+
obj.hasOwnProperty('timeCreated');
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
/**
|
|
382
|
+
* Auto-wraps response data if it contains doctree objects
|
|
383
|
+
* @private
|
|
384
|
+
*/
|
|
385
|
+
_autoWrapResponse(data) {
|
|
386
|
+
if (!data) return data;
|
|
387
|
+
|
|
388
|
+
// If response has an 'items' array (common pattern), wrap those
|
|
389
|
+
if (data.items && Array.isArray(data.items)) {
|
|
390
|
+
if (data.items.length > 0 && this._isDoctreeObject(data.items[0])) {
|
|
391
|
+
return {
|
|
392
|
+
...data,
|
|
393
|
+
items: this.wrapAsRbtObjects(data.items)
|
|
394
|
+
};
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
// If response has a single object property that looks like doctree
|
|
399
|
+
// Check common property names: board, ticket, task, note, etc.
|
|
400
|
+
for (const key of Object.keys(data)) {
|
|
401
|
+
if (this._isDoctreeObject(data[key])) {
|
|
402
|
+
return {
|
|
403
|
+
...data,
|
|
404
|
+
[key]: this.wrapAsRbtObjects(data[key])
|
|
405
|
+
};
|
|
406
|
+
}
|
|
407
|
+
// Check if it's an array of doctree objects
|
|
408
|
+
if (Array.isArray(data[key]) && data[key].length > 0 && this._isDoctreeObject(data[key][0])) {
|
|
409
|
+
return {
|
|
410
|
+
...data,
|
|
411
|
+
[key]: this.wrapAsRbtObjects(data[key])
|
|
412
|
+
};
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
// If the root data itself is a doctree object or array of them
|
|
417
|
+
if (this._isDoctreeObject(data)) {
|
|
418
|
+
return this.wrapAsRbtObjects(data);
|
|
419
|
+
}
|
|
420
|
+
if (Array.isArray(data) && data.length > 0 && this._isDoctreeObject(data[0])) {
|
|
421
|
+
return this.wrapAsRbtObjects(data);
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
return data;
|
|
425
|
+
}
|
|
426
|
+
|
|
309
427
|
//
|
|
310
428
|
// Get/Post to endpoint with Roboto authtoken
|
|
429
|
+
// Automatically wraps doctree objects in responses as RbtObjects
|
|
311
430
|
//
|
|
312
431
|
async get(endpoint, params){
|
|
313
|
-
|
|
432
|
+
const result = await this.api.get(endpoint, params);
|
|
433
|
+
return this._autoWrapResponse(result);
|
|
314
434
|
}
|
|
315
435
|
|
|
316
436
|
async post(endpoint, data){
|
|
317
|
-
|
|
437
|
+
const result = await this.api.post(endpoint, data);
|
|
438
|
+
return this._autoWrapResponse(result);
|
|
318
439
|
}
|
|
319
440
|
|
|
320
441
|
|
package/src/rbt_api.js
CHANGED
|
@@ -55,6 +55,8 @@ export default class RbtApi {
|
|
|
55
55
|
this.requestCache = {};
|
|
56
56
|
this._loadCurrentUserPromise = null;
|
|
57
57
|
this._loadCurrentUserExtendedPromise = null;
|
|
58
|
+
this.currentOrganization = null;
|
|
59
|
+
this._loadCurrentOrgPromise = null;
|
|
58
60
|
|
|
59
61
|
// Use the storageAdaptor to get the authToken, if available
|
|
60
62
|
this.initAuthToken(authtoken);
|
|
@@ -320,6 +322,14 @@ export default class RbtApi {
|
|
|
320
322
|
// authtoken is automatically stored for server-side access by the adapter
|
|
321
323
|
}
|
|
322
324
|
|
|
325
|
+
// Optionally load organization after login (can be disabled with loadOrganization: false)
|
|
326
|
+
if (params.loadOrganization !== false) {
|
|
327
|
+
// Kick off org load asynchronously but don't wait for it
|
|
328
|
+
this.loadCurrentOrganization().catch(err =>
|
|
329
|
+
console.warn('[RbtApi] Failed to load organization on login:', err)
|
|
330
|
+
);
|
|
331
|
+
}
|
|
332
|
+
|
|
323
333
|
return response.data;
|
|
324
334
|
|
|
325
335
|
} catch (e) {
|
|
@@ -348,6 +358,11 @@ export default class RbtApi {
|
|
|
348
358
|
if(this.localStorageAdaptor){
|
|
349
359
|
await this.localStorageAdaptor.setItem('authtoken', response.authToken);
|
|
350
360
|
}
|
|
361
|
+
|
|
362
|
+
// Load organization after OAuth login
|
|
363
|
+
this.loadCurrentOrganization().catch(err =>
|
|
364
|
+
console.warn('[RbtApi] Failed to load organization on OAuth login:', err)
|
|
365
|
+
);
|
|
351
366
|
|
|
352
367
|
return response;
|
|
353
368
|
|
|
@@ -371,6 +386,7 @@ export default class RbtApi {
|
|
|
371
386
|
// Clear the iac_session and remove the auth token from axios headers
|
|
372
387
|
this.iac_session = null;
|
|
373
388
|
this.currentUser = null;
|
|
389
|
+
this.currentOrganization = null;
|
|
374
390
|
this.authtoken = null;
|
|
375
391
|
if (this.axios.defaults.headers.common['authtoken']) {
|
|
376
392
|
delete this.axios.defaults.headers.common['authtoken'];
|
|
@@ -379,6 +395,8 @@ export default class RbtApi {
|
|
|
379
395
|
// Clear localStorage if it's being used
|
|
380
396
|
if (this.localStorageAdaptor) {
|
|
381
397
|
await this.localStorageAdaptor.removeItem('authtoken');
|
|
398
|
+
await this.localStorageAdaptor.removeItem('rbtUser');
|
|
399
|
+
await this.localStorageAdaptor.removeItem('currentOrgId');
|
|
382
400
|
}
|
|
383
401
|
|
|
384
402
|
// Return some kind of success response or the response from the server
|
|
@@ -494,6 +512,152 @@ export default class RbtApi {
|
|
|
494
512
|
|
|
495
513
|
}
|
|
496
514
|
|
|
515
|
+
/**
|
|
516
|
+
* Load current organization for the authenticated user
|
|
517
|
+
* Organization is determined by:
|
|
518
|
+
* 1. User's mod.currentOrgId preference
|
|
519
|
+
* 2. First organization in user.organizations array
|
|
520
|
+
* 3. null if user has no organizations
|
|
521
|
+
*
|
|
522
|
+
* @param {boolean} forceReload - Force reload from server even if cached
|
|
523
|
+
* @returns {Promise<RbtObject|null>} Organization object or null
|
|
524
|
+
*/
|
|
525
|
+
async loadCurrentOrganization(forceReload = false) {
|
|
526
|
+
try {
|
|
527
|
+
// Return cached if available and not forcing reload
|
|
528
|
+
if (this.currentOrganization && !forceReload) {
|
|
529
|
+
console.log('[RbtApi] Returning cached currentOrganization:', this.currentOrganization.get('name'));
|
|
530
|
+
return this.currentOrganization;
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
// Prevent duplicate concurrent loads
|
|
534
|
+
if (this._loadCurrentOrgPromise) {
|
|
535
|
+
console.log('[RbtApi] Organization load already in progress');
|
|
536
|
+
return this._loadCurrentOrgPromise;
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
this._loadCurrentOrgPromise = (async () => {
|
|
540
|
+
try {
|
|
541
|
+
// Ensure user is loaded first
|
|
542
|
+
const user = await this.loadCurrentUser();
|
|
543
|
+
if (!user) {
|
|
544
|
+
console.log('[RbtApi] No current user, cannot load organization');
|
|
545
|
+
return null;
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
// Get organization ID from user preferences or first org
|
|
549
|
+
const userData = user.getData();
|
|
550
|
+
// organizations array format: [{ id: 'org123', roles: ['owner'] }, ...]
|
|
551
|
+
const firstOrg = userData.organizations?.[0];
|
|
552
|
+
const orgId = userData.mod?.currentOrgId || (typeof firstOrg === 'object' ? firstOrg?.id : firstOrg);
|
|
553
|
+
|
|
554
|
+
if (!orgId) {
|
|
555
|
+
console.log('[RbtApi] User has no organizations');
|
|
556
|
+
return null;
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
console.log('[RbtApi] Resolved organization ID:', orgId, 'from:', {
|
|
560
|
+
modCurrentOrgId: userData.mod?.currentOrgId,
|
|
561
|
+
firstOrg: firstOrg
|
|
562
|
+
});
|
|
563
|
+
|
|
564
|
+
// Check if we have a cached org ID and it matches
|
|
565
|
+
if (this.localStorageAdaptor) {
|
|
566
|
+
const cachedOrgId = await this.localStorageAdaptor.getItem('currentOrgId');
|
|
567
|
+
if (cachedOrgId && cachedOrgId !== orgId) {
|
|
568
|
+
console.log('[RbtApi] Organization changed from cache:', cachedOrgId, '→', orgId);
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
// Load the organization object
|
|
573
|
+
console.log('[RbtApi] Loading organization:', orgId);
|
|
574
|
+
const org = await this.get('<@iac.organization>', orgId);
|
|
575
|
+
|
|
576
|
+
if (org) {
|
|
577
|
+
this.currentOrganization = org; // Already an RbtObject
|
|
578
|
+
console.log('[RbtApi] Current organization loaded:', org.get('name'));
|
|
579
|
+
|
|
580
|
+
// Cache in storage if available
|
|
581
|
+
if (this.localStorageAdaptor) {
|
|
582
|
+
await this.localStorageAdaptor.setItem('currentOrgId', orgId);
|
|
583
|
+
}
|
|
584
|
+
} else {
|
|
585
|
+
console.warn('[RbtApi] Organization not found or not accessible:', orgId);
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
return this.currentOrganization;
|
|
589
|
+
} catch (error) {
|
|
590
|
+
console.error('[RbtApi] Failed to load current organization:', error);
|
|
591
|
+
this.currentOrganization = null;
|
|
592
|
+
return null;
|
|
593
|
+
}
|
|
594
|
+
})();
|
|
595
|
+
|
|
596
|
+
const result = await this._loadCurrentOrgPromise;
|
|
597
|
+
this._loadCurrentOrgPromise = null;
|
|
598
|
+
return result;
|
|
599
|
+
} catch (error) {
|
|
600
|
+
this._loadCurrentOrgPromise = null;
|
|
601
|
+
return this._handleError(error);
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
/**
|
|
606
|
+
* Switch to a different organization
|
|
607
|
+
* Updates user preference and loads the new organization
|
|
608
|
+
*
|
|
609
|
+
* @param {string} orgId - Organization ID to switch to
|
|
610
|
+
* @returns {Promise<RbtObject>} The new current organization
|
|
611
|
+
*/
|
|
612
|
+
async switchOrganization(orgId) {
|
|
613
|
+
try {
|
|
614
|
+
if (!this.currentUser) {
|
|
615
|
+
throw new Error('Must be logged in to switch organization');
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
console.log('[RbtApi] Switching to organization:', orgId);
|
|
619
|
+
|
|
620
|
+
// Load the new organization
|
|
621
|
+
const org = await this.get('<@iac.organization>', orgId);
|
|
622
|
+
if (!org) {
|
|
623
|
+
throw new Error(`Organization ${orgId} not found or not accessible`);
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
this.currentOrganization = org;
|
|
627
|
+
|
|
628
|
+
// Save preference to user
|
|
629
|
+
try {
|
|
630
|
+
const userObj = await this.loadUser(this.currentUser.id);
|
|
631
|
+
userObj.set('mod.currentOrgId', orgId);
|
|
632
|
+
await userObj.save();
|
|
633
|
+
|
|
634
|
+
// Update cache
|
|
635
|
+
if (this.localStorageAdaptor) {
|
|
636
|
+
await this.localStorageAdaptor.setItem('currentOrgId', orgId);
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
console.log('[RbtApi] Switched to organization:', org.get('name'));
|
|
640
|
+
} catch (error) {
|
|
641
|
+
console.warn('[RbtApi] Failed to save organization preference:', error);
|
|
642
|
+
// Don't throw - switch succeeded even if preference save failed
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
return org;
|
|
646
|
+
} catch (error) {
|
|
647
|
+
return this._handleError(error);
|
|
648
|
+
}
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
/**
|
|
652
|
+
* Get current organization (null-safe)
|
|
653
|
+
* Returns cached organization without triggering a load
|
|
654
|
+
*
|
|
655
|
+
* @returns {RbtObject|null} Current organization or null
|
|
656
|
+
*/
|
|
657
|
+
getCurrentOrganization() {
|
|
658
|
+
return this.currentOrganization;
|
|
659
|
+
}
|
|
660
|
+
|
|
497
661
|
|
|
498
662
|
async loadUser(userId){
|
|
499
663
|
|