@iblai/data-layer 1.13.0 → 1.13.2
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/dist/index.d.ts +5 -1
- package/dist/index.esm.js +172 -24
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +172 -24
- package/dist/index.js.map +1 -1
- package/dist/package.json +1 -1
- package/dist/src/config.d.ts +6 -0
- package/dist/src/features/analytics/types.d.ts +4 -0
- package/dist/src/features/auth/__tests__/token-refresh.test.d.ts +1 -0
- package/dist/src/features/auth/token-refresh.d.ts +11 -0
- package/dist/src/utils/index.d.ts +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -39144,6 +39144,12 @@ Config.axdUrl = 'https://base.manager.iblai.app';
|
|
|
39144
39144
|
Config.mentorIframeUrl = 'https://mentor.iblai.tech';
|
|
39145
39145
|
Config.studioUrl = 'https://studio.iblai.app';
|
|
39146
39146
|
Config.httpErrorHandlers = {};
|
|
39147
|
+
/**
|
|
39148
|
+
* Optional override for how expired auth tokens are re-minted after a 401.
|
|
39149
|
+
* Resolve `true` once fresh tokens are in storage. Left unset, the data
|
|
39150
|
+
* layer mints them itself from the edX session.
|
|
39151
|
+
*/
|
|
39152
|
+
Config.refreshAuthToken = undefined;
|
|
39147
39153
|
|
|
39148
39154
|
class IblDataLayer {
|
|
39149
39155
|
}
|
|
@@ -39198,6 +39204,133 @@ const iblFakeBaseQuery = react.retry(fakeBaseQueryWithLogging, {
|
|
|
39198
39204
|
},
|
|
39199
39205
|
});
|
|
39200
39206
|
|
|
39207
|
+
const AUTH_ENDPOINTS = {
|
|
39208
|
+
GET_MFE_CONTEXT: {
|
|
39209
|
+
service: exports.SERVICES.LMS,
|
|
39210
|
+
path: () => `/api/mfe_context`,
|
|
39211
|
+
},
|
|
39212
|
+
APPLE_EXCHANGE_TOKEN: {
|
|
39213
|
+
service: exports.SERVICES.LMS,
|
|
39214
|
+
path: () => `/oauth2/exchange_access_token/apple-id/`,
|
|
39215
|
+
},
|
|
39216
|
+
GET_APP_TOKENS: {
|
|
39217
|
+
service: exports.SERVICES.LMS,
|
|
39218
|
+
path: () => '/api/ibl/manager/consolidated-token/proxy/',
|
|
39219
|
+
},
|
|
39220
|
+
REFRESH_JWT_TOKEN: {
|
|
39221
|
+
service: exports.SERVICES.LEGACY_LMS,
|
|
39222
|
+
path: () => '/ibl-auth/request-jwt/',
|
|
39223
|
+
},
|
|
39224
|
+
};
|
|
39225
|
+
const AUTH_REDUCER_PATH = 'authApiSlice';
|
|
39226
|
+
|
|
39227
|
+
/**
|
|
39228
|
+
* Expiry keys are written alongside the tokens so `hasNonExpiredAuthToken()`
|
|
39229
|
+
* and friends don't treat a freshly minted token as stale.
|
|
39230
|
+
*/
|
|
39231
|
+
const AXD_TOKEN_EXPIRES_KEY = 'axd_token_expires';
|
|
39232
|
+
const DM_TOKEN_EXPIRES_KEY = 'dm_token_expires';
|
|
39233
|
+
/** Reads the platform key the tokens should be minted for. */
|
|
39234
|
+
const readPlatformKey = async () => {
|
|
39235
|
+
var _a;
|
|
39236
|
+
const storage = IblDataLayer.storage;
|
|
39237
|
+
if (!storage)
|
|
39238
|
+
return null;
|
|
39239
|
+
const raw = await storage.getItem(STORAGE_KEYS.CURRENT_TENANT);
|
|
39240
|
+
if (raw) {
|
|
39241
|
+
try {
|
|
39242
|
+
const key = (_a = JSON.parse(raw)) === null || _a === void 0 ? void 0 : _a.key;
|
|
39243
|
+
if (key)
|
|
39244
|
+
return key;
|
|
39245
|
+
}
|
|
39246
|
+
catch (_b) {
|
|
39247
|
+
// `current_tenant` is occasionally stored as a bare slug rather than the
|
|
39248
|
+
// serialised tenant object; use it as-is.
|
|
39249
|
+
if (raw.trim())
|
|
39250
|
+
return raw;
|
|
39251
|
+
}
|
|
39252
|
+
}
|
|
39253
|
+
return (await storage.getItem(STORAGE_KEYS.TENANT)) || null;
|
|
39254
|
+
};
|
|
39255
|
+
/**
|
|
39256
|
+
* Mints a fresh set of app tokens from the edX session and writes them to
|
|
39257
|
+
* storage. Deliberately uses `fetch` rather than the data layer's own base
|
|
39258
|
+
* query: routing it through `iblFetchBaseQuery` would re-enter the 401
|
|
39259
|
+
* handling this function exists to serve.
|
|
39260
|
+
*/
|
|
39261
|
+
const defaultRefreshAuthToken = async () => {
|
|
39262
|
+
var _a, _b;
|
|
39263
|
+
const storage = IblDataLayer.storage;
|
|
39264
|
+
if (!storage)
|
|
39265
|
+
return false;
|
|
39266
|
+
const platformKey = await readPlatformKey();
|
|
39267
|
+
if (!platformKey) {
|
|
39268
|
+
console.warn('[token-refresh] No platform key in storage, cannot refresh');
|
|
39269
|
+
return false;
|
|
39270
|
+
}
|
|
39271
|
+
// The consolidated-token endpoint authenticates with the edX JWT, not with
|
|
39272
|
+
// the axd/dm tokens that just expired.
|
|
39273
|
+
const authHeaders = await getHeaders(AUTH_ENDPOINTS.GET_APP_TOKENS.service);
|
|
39274
|
+
if (!(authHeaders === null || authHeaders === void 0 ? void 0 : authHeaders.Authorization)) {
|
|
39275
|
+
console.warn('[token-refresh] No edX token available, cannot refresh');
|
|
39276
|
+
return false;
|
|
39277
|
+
}
|
|
39278
|
+
const formData = new FormData();
|
|
39279
|
+
formData.append('platform_key', platformKey);
|
|
39280
|
+
const url = `${getServiceUrl(AUTH_ENDPOINTS.GET_APP_TOKENS.service)}${AUTH_ENDPOINTS.GET_APP_TOKENS.path()}`;
|
|
39281
|
+
const response = await fetch(url, {
|
|
39282
|
+
method: 'POST',
|
|
39283
|
+
headers: { Authorization: authHeaders.Authorization },
|
|
39284
|
+
body: formData,
|
|
39285
|
+
});
|
|
39286
|
+
if (!response.ok) {
|
|
39287
|
+
console.warn(`[token-refresh] Mint failed with ${response.status}`);
|
|
39288
|
+
return false;
|
|
39289
|
+
}
|
|
39290
|
+
const body = (await response.json());
|
|
39291
|
+
const axdToken = (_a = body === null || body === void 0 ? void 0 : body.data) === null || _a === void 0 ? void 0 : _a.axd_token;
|
|
39292
|
+
const dmToken = (_b = body === null || body === void 0 ? void 0 : body.data) === null || _b === void 0 ? void 0 : _b.dm_token;
|
|
39293
|
+
if (!(axdToken === null || axdToken === void 0 ? void 0 : axdToken.token) || !(dmToken === null || dmToken === void 0 ? void 0 : dmToken.token)) {
|
|
39294
|
+
console.warn('[token-refresh] Mint returned no tokens');
|
|
39295
|
+
return false;
|
|
39296
|
+
}
|
|
39297
|
+
await storage.setItem(STORAGE_KEYS.AXD_TOKEN_KEY, axdToken.token);
|
|
39298
|
+
await storage.setItem(STORAGE_KEYS.DM_TOKEN_KEY, dmToken.token);
|
|
39299
|
+
if (axdToken.expires) {
|
|
39300
|
+
await storage.setItem(AXD_TOKEN_EXPIRES_KEY, axdToken.expires);
|
|
39301
|
+
}
|
|
39302
|
+
if (dmToken.expires) {
|
|
39303
|
+
await storage.setItem(DM_TOKEN_EXPIRES_KEY, dmToken.expires);
|
|
39304
|
+
}
|
|
39305
|
+
return true;
|
|
39306
|
+
};
|
|
39307
|
+
/** The refresh currently in flight, if any. */
|
|
39308
|
+
let refreshInFlight = null;
|
|
39309
|
+
/**
|
|
39310
|
+
* Refreshes the auth tokens, collapsing concurrent callers onto one request.
|
|
39311
|
+
* A page load that fires ten requests in parallel produces ten 401s; without
|
|
39312
|
+
* this they would mint ten token sets and race each other into storage.
|
|
39313
|
+
*
|
|
39314
|
+
* Never rejects — a thrown refresh resolves `false`, which callers read as
|
|
39315
|
+
* "give up and run the registered 401 handler" (i.e. log out).
|
|
39316
|
+
*/
|
|
39317
|
+
const refreshAuthTokenOnce = async () => {
|
|
39318
|
+
var _a;
|
|
39319
|
+
if (!refreshInFlight) {
|
|
39320
|
+
const refresh = (_a = Config.refreshAuthToken) !== null && _a !== void 0 ? _a : defaultRefreshAuthToken;
|
|
39321
|
+
refreshInFlight = Promise.resolve()
|
|
39322
|
+
.then(() => refresh())
|
|
39323
|
+
.catch((error) => {
|
|
39324
|
+
console.error('[token-refresh] Refresh threw:', error);
|
|
39325
|
+
return false;
|
|
39326
|
+
})
|
|
39327
|
+
.finally(() => {
|
|
39328
|
+
refreshInFlight = null;
|
|
39329
|
+
});
|
|
39330
|
+
}
|
|
39331
|
+
return refreshInFlight;
|
|
39332
|
+
};
|
|
39333
|
+
|
|
39201
39334
|
/**
|
|
39202
39335
|
* Get the service URL based on the provided service type.
|
|
39203
39336
|
*/
|
|
@@ -39292,6 +39425,7 @@ const buildEndpointFromService = (service, serviceFn, options) => {
|
|
|
39292
39425
|
async queryFn(args) {
|
|
39293
39426
|
var _a;
|
|
39294
39427
|
let lastError;
|
|
39428
|
+
let refreshAttempted = false;
|
|
39295
39429
|
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
|
39296
39430
|
try {
|
|
39297
39431
|
OpenAPI.BASE = getServiceUrl(service);
|
|
@@ -39303,6 +39437,17 @@ const buildEndpointFromService = (service, serviceFn, options) => {
|
|
|
39303
39437
|
catch (err) {
|
|
39304
39438
|
lastError = err;
|
|
39305
39439
|
const status = err === null || err === void 0 ? void 0 : err.status;
|
|
39440
|
+
// An expired token is recoverable: mint a new one and replay the
|
|
39441
|
+
// call (headers are rebuilt at the top of the loop) rather than
|
|
39442
|
+
// dropping straight to the 401 handler, which logs the user out.
|
|
39443
|
+
// One attempt per call, so a token the server keeps rejecting can't
|
|
39444
|
+
// loop.
|
|
39445
|
+
if (status === 401 && !refreshAttempted) {
|
|
39446
|
+
refreshAttempted = true;
|
|
39447
|
+
if (await refreshAuthTokenOnce()) {
|
|
39448
|
+
continue;
|
|
39449
|
+
}
|
|
39450
|
+
}
|
|
39306
39451
|
// Don't retry auth errors, statuses the endpoint expects as an
|
|
39307
39452
|
// empty state, or a spend-cap block (fail fast — no retry storm).
|
|
39308
39453
|
if (QUERYFN_NON_RETRYABLE_STATUSES.includes(status) ||
|
|
@@ -39457,10 +39602,23 @@ const withNonRetryableErrors = (query$1) => async (args, api, extraOptions) => {
|
|
|
39457
39602
|
const iblFetchBaseQuery = async (args, api, extraOptions) => {
|
|
39458
39603
|
var _a, _b, _c;
|
|
39459
39604
|
try {
|
|
39605
|
+
const runQuery = () => {
|
|
39606
|
+
var _a;
|
|
39607
|
+
return query.retry(withNonRetryableErrors(baseQuery(args.service, (_a = args === null || args === void 0 ? void 0 : args.isJson) !== null && _a !== void 0 ? _a : true, args === null || args === void 0 ? void 0 : args.contentType, args === null || args === void 0 ? void 0 : args.skipAuth)), {
|
|
39608
|
+
maxRetries: 3,
|
|
39609
|
+
})(args, api, extraOptions);
|
|
39610
|
+
};
|
|
39460
39611
|
// Executing base query
|
|
39461
|
-
|
|
39462
|
-
|
|
39463
|
-
|
|
39612
|
+
let result = await runQuery();
|
|
39613
|
+
// An expired token is recoverable: mint a new one and replay the request
|
|
39614
|
+
// (`prepareHeaders` re-reads storage per call) rather than dropping
|
|
39615
|
+
// straight to the 401 handler, which logs the user out. One attempt per
|
|
39616
|
+
// request, so a token the server keeps rejecting can't loop.
|
|
39617
|
+
if (((_a = result.error) === null || _a === void 0 ? void 0 : _a.status) === 401 && !(args === null || args === void 0 ? void 0 : args.skipAuth)) {
|
|
39618
|
+
if (await refreshAuthTokenOnce()) {
|
|
39619
|
+
result = await runQuery();
|
|
39620
|
+
}
|
|
39621
|
+
}
|
|
39464
39622
|
// Query completed
|
|
39465
39623
|
if (result.error) {
|
|
39466
39624
|
console.log('[MONETIZATION ERROR HANDLER]: ', { result });
|
|
@@ -39534,6 +39692,7 @@ const buildEndpointFromServiceLegacy = (service, serviceFn, options) => {
|
|
|
39534
39692
|
return {
|
|
39535
39693
|
async queryFn(args) {
|
|
39536
39694
|
let lastError;
|
|
39695
|
+
let refreshAttempted = false;
|
|
39537
39696
|
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
|
39538
39697
|
try {
|
|
39539
39698
|
OpenAPI.BASE = getServiceUrl(service);
|
|
@@ -39545,6 +39704,14 @@ const buildEndpointFromServiceLegacy = (service, serviceFn, options) => {
|
|
|
39545
39704
|
catch (err) {
|
|
39546
39705
|
lastError = err;
|
|
39547
39706
|
const status = err === null || err === void 0 ? void 0 : err.status;
|
|
39707
|
+
// See the note in `buildEndpointFromService`: refresh once and
|
|
39708
|
+
// replay before letting a 401 reach the logout handler.
|
|
39709
|
+
if (status === 401 && !refreshAttempted) {
|
|
39710
|
+
refreshAttempted = true;
|
|
39711
|
+
if (await refreshAuthTokenOnce()) {
|
|
39712
|
+
continue;
|
|
39713
|
+
}
|
|
39714
|
+
}
|
|
39548
39715
|
if (QUERYFN_NON_RETRYABLE_STATUSES.includes(status) ||
|
|
39549
39716
|
expectedErrorStatuses.includes(status)) {
|
|
39550
39717
|
break;
|
|
@@ -40229,26 +40396,6 @@ const promptsApiSlice = react.createApi({
|
|
|
40229
40396
|
});
|
|
40230
40397
|
const { useGetPromptCategoriesQuery, useLazyGetPromptCategoriesQuery, useGetPromptsQuery, useLazyGetPromptsQuery, useGetPromptsSearchQuery, useLazyGetPromptsSearchQuery, useCreatePromptMutation, useUpdatePromptMutation, useDeletePromptMutation, useGetGuidedPromptsQuery, useLazyGetGuidedPromptsQuery, } = promptsApiSlice;
|
|
40231
40398
|
|
|
40232
|
-
const AUTH_ENDPOINTS = {
|
|
40233
|
-
GET_MFE_CONTEXT: {
|
|
40234
|
-
service: exports.SERVICES.LMS,
|
|
40235
|
-
path: () => `/api/mfe_context`,
|
|
40236
|
-
},
|
|
40237
|
-
APPLE_EXCHANGE_TOKEN: {
|
|
40238
|
-
service: exports.SERVICES.LMS,
|
|
40239
|
-
path: () => `/oauth2/exchange_access_token/apple-id/`,
|
|
40240
|
-
},
|
|
40241
|
-
GET_APP_TOKENS: {
|
|
40242
|
-
service: exports.SERVICES.LMS,
|
|
40243
|
-
path: () => '/api/ibl/manager/consolidated-token/proxy/',
|
|
40244
|
-
},
|
|
40245
|
-
REFRESH_JWT_TOKEN: {
|
|
40246
|
-
service: exports.SERVICES.LEGACY_LMS,
|
|
40247
|
-
path: () => '/ibl-auth/request-jwt/',
|
|
40248
|
-
},
|
|
40249
|
-
};
|
|
40250
|
-
const AUTH_REDUCER_PATH = 'authApiSlice';
|
|
40251
|
-
|
|
40252
40399
|
const authApiSlice = react.createApi({
|
|
40253
40400
|
reducerPath: AUTH_REDUCER_PATH,
|
|
40254
40401
|
baseQuery: iblFetchBaseQuery,
|
|
@@ -48208,7 +48355,7 @@ const skillsMiddleware = [
|
|
|
48208
48355
|
userChatHistoryApiSlice.middleware,
|
|
48209
48356
|
];
|
|
48210
48357
|
|
|
48211
|
-
const initializeDataLayer = (dmUrl, lmsUrl, legacyLmsUrl, storageService, httpErrorHandler = {}, studioUrl) => {
|
|
48358
|
+
const initializeDataLayer = (dmUrl, lmsUrl, legacyLmsUrl, storageService, httpErrorHandler = {}, studioUrl, refreshAuthToken) => {
|
|
48212
48359
|
IblDataLayer.storage = storageService;
|
|
48213
48360
|
Config.dmUrl = dmUrl;
|
|
48214
48361
|
Config.lmsUrl = lmsUrl;
|
|
@@ -48217,6 +48364,7 @@ const initializeDataLayer = (dmUrl, lmsUrl, legacyLmsUrl, storageService, httpEr
|
|
|
48217
48364
|
if (studioUrl) {
|
|
48218
48365
|
Config.studioUrl = studioUrl;
|
|
48219
48366
|
}
|
|
48367
|
+
Config.refreshAuthToken = refreshAuthToken;
|
|
48220
48368
|
};
|
|
48221
48369
|
|
|
48222
48370
|
exports.AGENT_SKILLS_ENDPOINTS = AGENT_SKILLS_ENDPOINTS;
|