@crimson-education/sdk 0.3.7 → 0.3.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,5 +1,5 @@
1
1
  import type { CrimsonClient } from "./client";
2
- import type { CurrentUserRoles, StudentSummary, UserProfile } from "./types";
2
+ import type { CurrentUserRoles, StudentSummary, UserProfile, LinkedTenantsResponse } from "./types";
3
3
  export declare class AccountApi {
4
4
  private client;
5
5
  constructor(client: CrimsonClient);
@@ -37,4 +37,10 @@ export declare class AccountApi {
37
37
  * @returns User profile information
38
38
  */
39
39
  getMyProfile(): Promise<UserProfile>;
40
+ /**
41
+ * Get the list of tenants linked to the current user
42
+ *
43
+ * @returns List of linked tenants and current tenant info
44
+ */
45
+ getLinkedTenants(): Promise<LinkedTenantsResponse>;
40
46
  }
@@ -62,5 +62,15 @@ class AccountApi {
62
62
  return this.client.fetch("/api/v1/account/me/profile");
63
63
  });
64
64
  }
65
+ /**
66
+ * Get the list of tenants linked to the current user
67
+ *
68
+ * @returns List of linked tenants and current tenant info
69
+ */
70
+ getLinkedTenants() {
71
+ return __awaiter(this, void 0, void 0, function* () {
72
+ return this.client.fetch("/api/v1/account/linked-tenants");
73
+ });
74
+ }
65
75
  }
66
76
  exports.AccountApi = AccountApi;
@@ -19,12 +19,19 @@ export declare class CrimsonClient {
19
19
  private appName;
20
20
  private appTenant;
21
21
  private oauthAdapter?;
22
+ private tenantDomain?;
23
+ private isResolvingDomain;
24
+ private tenantDomainPromise?;
22
25
  constructor(config: CrimsonClientConfig);
23
26
  /**
24
27
  * Initialize the client (required for OAuth mode)
25
28
  * Call this after creating the client to load stored tokens
26
29
  */
27
30
  initialize(): Promise<void>;
31
+ /**
32
+ * Resolve the x-tenant-domain dynamically
33
+ */
34
+ resolveTenantDomain(): Promise<void>;
28
35
  /**
29
36
  * Get the OAuth adapter (only available in OAuth mode)
30
37
  */
@@ -45,6 +45,7 @@ function isOAuthConfig(config) {
45
45
  class CrimsonClient {
46
46
  constructor(config) {
47
47
  this.config = config;
48
+ this.isResolvingDomain = false;
48
49
  this.missions = new missions_1.MissionsApi(this);
49
50
  this.tasks = new tasks_1.TasksApi(this);
50
51
  this.roadmap = new roadmap_1.RoadmapApi(this);
@@ -91,6 +92,55 @@ class CrimsonClient {
91
92
  }
92
93
  });
93
94
  }
95
+ /**
96
+ * Resolve the x-tenant-domain dynamically
97
+ */
98
+ resolveTenantDomain() {
99
+ return __awaiter(this, void 0, void 0, function* () {
100
+ if (this.tenantDomain)
101
+ return;
102
+ if (!this.tenantDomainPromise) {
103
+ this.isResolvingDomain = true;
104
+ this.tenantDomainPromise = (() => __awaiter(this, void 0, void 0, function* () {
105
+ var _a, _b;
106
+ try {
107
+ const token = yield this.getToken();
108
+ if (!token) {
109
+ // Silently return without error if called early;
110
+ // it will be lazily retried in fetch() or when token is received.
111
+ this.tenantDomainPromise = undefined;
112
+ return;
113
+ }
114
+ // A newer version of profile will return domain
115
+ const profile = yield this.account.getMyProfile();
116
+ if ((_a = profile.tenant) === null || _a === void 0 ? void 0 : _a.domain) {
117
+ this.tenantDomain = profile.tenant.domain;
118
+ }
119
+ else {
120
+ // Use getLinkedtenants as a safe fallback when getMyProfile does not return a domain.
121
+ // Can be removed later.
122
+ const linkedRes = yield this.account.getLinkedTenants();
123
+ if (linkedRes.linkedTenants && linkedRes.linkedTenants.length > 0) {
124
+ // find domain.tenantId equals to currentTenantId
125
+ const domain = (_b = linkedRes.linkedTenants.find(t => t.tenantId === linkedRes.currentTenantId)) === null || _b === void 0 ? void 0 : _b.tenantDomain;
126
+ if (domain) {
127
+ this.tenantDomain = domain;
128
+ }
129
+ }
130
+ }
131
+ }
132
+ catch (err) {
133
+ console.warn('[CrimsonSDK] Failed to resolve tenant domain:', err);
134
+ this.tenantDomainPromise = undefined;
135
+ }
136
+ finally {
137
+ this.isResolvingDomain = false;
138
+ }
139
+ }))();
140
+ }
141
+ return this.tenantDomainPromise;
142
+ });
143
+ }
94
144
  /**
95
145
  * Get the OAuth adapter (only available in OAuth mode)
96
146
  */
@@ -192,6 +242,10 @@ class CrimsonClient {
192
242
  }
193
243
  fetch(path_1) {
194
244
  return __awaiter(this, arguments, void 0, function* (path, options = {}) {
245
+ // If it's a roadmap endpoint, wait for tenant domain to be resolved first
246
+ if (path.includes("/roadmap")) {
247
+ yield this.resolveTenantDomain();
248
+ }
195
249
  const token = yield this.getToken();
196
250
  if (!token) {
197
251
  throw new Error("No authentication token available");
@@ -207,6 +261,10 @@ class CrimsonClient {
207
261
  if (this.appTenant) {
208
262
  headers["X-App-Tenant"] = this.appTenant;
209
263
  }
264
+ console.log(`tenantDomain = ${this.tenantDomain}`);
265
+ if (this.tenantDomain) {
266
+ headers["x-tenant-domain"] = this.tenantDomain;
267
+ }
210
268
  const url = `${this.baseUrl}${path.startsWith("/") ? path : `/${path}`}`;
211
269
  const response = yield fetch(url, Object.assign(Object.assign({}, options), { headers }));
212
270
  if (!response.ok) {
@@ -494,8 +494,22 @@ export interface UserProfile {
494
494
  tenant: {
495
495
  id: string;
496
496
  name: string;
497
+ domain?: string;
497
498
  };
498
499
  }
500
+ export interface LinkedTenant {
501
+ tenantId: string;
502
+ tenantName: string;
503
+ tenantDomain: string | null;
504
+ tenantLevel: number;
505
+ isCurrent: boolean;
506
+ userEmail: string;
507
+ }
508
+ export interface LinkedTenantsResponse {
509
+ currentUserId: string;
510
+ currentTenantId: string;
511
+ linkedTenants: LinkedTenant[];
512
+ }
499
513
  /**
500
514
  * Prefilled field with value and source information
501
515
  */
@@ -36,8 +36,18 @@ function CrimsonProvider({ children, apiUrl, allowedParentOrigins, queryClient:
36
36
  }), [apiUrl, clientId, appName, appTenant]);
37
37
  (0, react_1.useEffect)(() => {
38
38
  const origins = allowedParentOrigins || (0, iframe_1.getDefaultAllowedOrigins)();
39
- const cleanup = (0, iframe_1.setupIframeListener)(origins);
40
- return cleanup;
41
- }, [allowedParentOrigins]);
39
+ const cleanupIframe = (0, iframe_1.setupIframeListener)(origins);
40
+ // Eagerly resolve tenant domain as soon as token becomes available from iframe
41
+ const cleanupAuth = (0, iframe_1.subscribeToAuthState)(() => {
42
+ const state = (0, iframe_1.getAuthState)();
43
+ if (state.token) {
44
+ client.resolveTenantDomain().catch(() => { });
45
+ }
46
+ });
47
+ return () => {
48
+ cleanupIframe();
49
+ cleanupAuth();
50
+ };
51
+ }, [allowedParentOrigins, client]);
42
52
  return ((0, jsx_runtime_1.jsx)(CrimsonContext.Provider, { value: { client }, children: (0, jsx_runtime_1.jsx)(react_query_1.QueryClientProvider, { client: queryClient, children: children }) }));
43
53
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@crimson-education/sdk",
3
- "version": "0.3.7",
3
+ "version": "0.3.9",
4
4
  "description": "Crimson SDK for accessing Crimson App APIs",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.js",