roboto-js 1.9.5 → 1.9.9

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.
@@ -1,4 +1,4 @@
1
1
  // Auto-generated version file
2
2
  // DO NOT EDIT - This file is automatically updated from package.json
3
- // Version: 1.9.5
4
- export var version = '1.9.5';
3
+ // Version: 1.9.9
4
+ export var version = '1.9.9';
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
- return _context18.a(2, this.api.get(endpoint, params));
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
- return _context19.a(2, this.api.post(endpoint, data));
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/version.js CHANGED
@@ -1,4 +1,4 @@
1
1
  // Auto-generated version file
2
2
  // DO NOT EDIT - This file is automatically updated from package.json
3
- // Version: 1.9.4
4
- export var version = '1.9.4';
3
+ // Version: 1.9.5
4
+ export var version = '1.9.5';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "roboto-js",
3
- "version": "1.9.5",
3
+ "version": "1.9.9",
4
4
  "type": "module",
5
5
  "description": "",
6
6
  "main": "dist/cjs/index.cjs",
@@ -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
- console.log(`[CookieStorageAdaptor] getItem(${key}): cookie "${cookieName}" not found, returning null`)
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
@@ -299,6 +299,25 @@ export default class Roboto{
299
299
  return this.api.confirmUserEmail(params);
300
300
  }
301
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
+ async selectCurrentOrganization(orgId, role = 'owner'){
312
+ return this.api.selectCurrentOrganization(orgId, role);
313
+ }
314
+ getCurrentOrganization(){
315
+ return this.api.getCurrentOrganization();
316
+ }
317
+ get currentOrganization(){
318
+ return this.api.currentOrganization;
319
+ }
320
+
302
321
  //
303
322
  // create and upload files
304
323
  //
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,233 @@ 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
+
661
+ /**
662
+ * Select and link an organization to the current user
663
+ * This adds the organization to the user's organizations array (if not already there)
664
+ * and sets it as the current organization preference
665
+ *
666
+ * @param {string} orgId - Organization ID to select
667
+ * @param {string} role - Role for the user in this organization (default: 'owner')
668
+ * @returns {Promise<RbtObject>} The selected organization
669
+ */
670
+ async selectCurrentOrganization(orgId, role = 'owner') {
671
+ try {
672
+ // Ensure we have a current user
673
+ if (!this.currentUser) {
674
+ console.log('[RbtApi] Loading current user for organization selection...');
675
+ await this.loadCurrentUser();
676
+ if (!this.currentUser) {
677
+ throw new Error('Must be logged in to select organization');
678
+ }
679
+ }
680
+
681
+ console.log('[RbtApi] Selecting organization:', orgId);
682
+
683
+ // Load the organization to verify access
684
+ const org = await this.get('<@iac.organization>', orgId);
685
+ if (!org) {
686
+ throw new Error(`Organization ${orgId} not found or not accessible`);
687
+ }
688
+
689
+ // Load the user object using loadUser to get a saveable instance
690
+ const user = await this.loadUser(this.currentUser.id);
691
+ if (!user) {
692
+ throw new Error('Failed to load user object');
693
+ }
694
+
695
+ console.log('[RbtApi] User loaded:', user.id);
696
+
697
+ // Update user's organizations array
698
+ const userOrgs = user.get('organizations') || [];
699
+ const orgExists = userOrgs.some(o => o?.id === orgId);
700
+
701
+ if (!orgExists) {
702
+ console.log('[RbtApi] Adding organization to user organizations array');
703
+ userOrgs.unshift({ id: orgId, roles: [role] });
704
+ user.set('organizations', userOrgs);
705
+ } else {
706
+ console.log('[RbtApi] Organization already in user organizations array');
707
+ }
708
+
709
+ // Set as current organization preference - handle both nested path formats
710
+ const modData = user.get('mod') || {};
711
+ modData.currentOrgId = orgId;
712
+ user.set('mod', modData);
713
+
714
+ // Save user record
715
+ console.log('[RbtApi] Saving user with organization:', orgId);
716
+ await user.save();
717
+ console.log('[RbtApi] User saved successfully');
718
+
719
+ // Clear cached organization to force reload
720
+ this.currentOrganization = null;
721
+
722
+ // Update the current user reference
723
+ this.currentUser = user;
724
+
725
+ // Set the new current organization
726
+ this.currentOrganization = org;
727
+
728
+ // Update cache
729
+ if (this.localStorageAdaptor) {
730
+ await this.localStorageAdaptor.setItem('currentOrgId', orgId);
731
+ }
732
+
733
+ console.log('[RbtApi] Selected organization:', org.get('name'));
734
+
735
+ return org;
736
+ } catch (error) {
737
+ console.error('[RbtApi] Error in selectCurrentOrganization:', error);
738
+ return this._handleError(error);
739
+ }
740
+ }
741
+
497
742
 
498
743
  async loadUser(userId){
499
744
 
package/src/version.js CHANGED
@@ -1,4 +1,4 @@
1
1
  // Auto-generated version file
2
2
  // DO NOT EDIT - This file is automatically updated from package.json
3
- // Version: 1.9.5
4
- export const version = '1.9.5';
3
+ // Version: 1.9.9
4
+ export const version = '1.9.9';