ltijs
Version:
Easily turn your web application into a LTI 1.3 Learning Tool.
127 lines • 6.44 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.NamesAndRoles = void 0;
const link_header_1 = require("#utils/request/link-header");
const authorization_header_1 = require("#utils/request/authorization-header");
const validation_1 = require("#utils/validation/validation");
const id_token_constants_1 = require("#services/launch/id-token.constants");
const lti_scopes_constants_1 = require("#shared/lti-scopes.constants");
const names_and_roles_schemas_1 = require("#services/names-and-roles/names-and-roles.schemas");
const names_and_roles_serializer_1 = require("#services/names-and-roles/names-and-roles.serializer");
const errors_1 = require("#services/names-and-roles/errors");
const errors_2 = require("#shared/errors");
class NamesAndRoles {
LOG_COMPONENT = 'namesAndRolesService';
NRPS_ACCEPT = 'application/vnd.ims.lti-nrps.v2.membershipcontainer+json';
ROLE_PARAM = 'role';
LIMIT_PARAM = 'limit';
RESOURCE_LINK_ID_PARAM = 'rlid';
launchContext;
accessTokenManager;
requestHandler;
logger;
constructor(launchContext, accessTokenManager, requestHandler, logger) {
this.launchContext = launchContext;
this.accessTokenManager = accessTokenManager;
this.requestHandler = requestHandler;
this.logger = logger;
}
/** Whether this launch declared NRPS support. Check before calling any other method on this service. */
isAvailable() {
return this.launchContext.idToken.services.namesAndRoles.available;
}
async getMembers(options) {
this.ensureServiceAvailability();
const { platform, rawIdToken: idToken } = this.launchContext;
const request = this.buildMembershipsRequest(idToken, options);
this.logger.debug(this.LOG_COMPONENT, 'Attempting to retrieve memberships');
this.logger.debug(this.LOG_COMPONENT, 'Target platform: ' + idToken.iss);
const accessToken = await this.accessTokenManager.getAccessToken(platform, lti_scopes_constants_1.NRPS_CONTEXT_MEMBERSHIP_READONLY_SCOPE);
this.logger.debug(this.LOG_COMPONENT, 'Access_token retrieved for [' + idToken.iss + ']');
const result = await this.fetchPages(request, accessToken, this.resolveNumberOfPages(options?.pages));
return (0, names_and_roles_serializer_1.buildMemberships)(result);
}
ensureServiceAvailability() {
if (!this.isAvailable())
throw new errors_1.NamesAndRolesNotAvailableError();
}
resolveNumberOfPages(pages) {
if (pages === false)
return false;
return pages === undefined || pages < 1 ? 1 : pages;
}
buildMembershipsRequest(idToken, options) {
const namesRoles = idToken[id_token_constants_1.IdTokenClaim.NamesRoleService];
const namesRolesUrl = namesRoles?.context_memberships_url;
if (namesRolesUrl === undefined)
throw new errors_1.MissingNamesRolesServiceUrlError();
const pageUrl = namesRolesUrl;
const query = new URLSearchParams();
if (options === undefined)
return { pageUrl, query };
if (options.url !== undefined && options.url !== '')
return { pageUrl: options.url, query };
if (options.role !== undefined && options.role !== '') {
this.logger.debug(this.LOG_COMPONENT, 'Adding role parameter with value: ' + options.role);
query.append(this.ROLE_PARAM, options.role);
}
if (options.limit !== undefined && options.limit > 0) {
this.logger.debug(this.LOG_COMPONENT, 'Adding limit parameter with value: ' + String(options.limit));
query.append(this.LIMIT_PARAM, String(options.limit));
}
if (options.resourceLinkId !== undefined && options.resourceLinkId !== false && options.resourceLinkId !== '') {
const resourceLinkId = options.resourceLinkId === true ? idToken[id_token_constants_1.IdTokenClaim.ResourceLink]?.id : options.resourceLinkId;
if (resourceLinkId === undefined || resourceLinkId === '')
throw new errors_2.MissingOrInvalidResourceLinkIdError();
this.logger.debug(this.LOG_COMPONENT, 'Adding rlid parameter with value: ' + resourceLinkId);
query.append(this.RESOURCE_LINK_ID_PARAM, resourceLinkId);
}
return { pageUrl, query };
}
async fetchPages(request, accessToken, numberOfPages) {
let pageUrl = request.pageUrl;
let differencesUrl;
let result;
let pageNumber = 1;
do {
if (numberOfPages !== false && pageNumber > numberOfPages) {
if (result !== undefined)
result.next = pageUrl;
break;
}
this.logger.debug(this.LOG_COMPONENT, `Fetching membership page ${pageNumber}: ${pageUrl}`);
const page = await this.fetchPage(pageUrl, pageNumber === 1 ? request.query : undefined, accessToken);
if (result === undefined)
result = page.memberships;
else
result.members = [...result.members, ...page.memberships.members];
if (page.differences !== undefined)
differencesUrl = page.differences;
pageUrl = page.next;
pageNumber++;
} while (pageUrl !== undefined);
if (result === undefined)
throw new errors_1.MembersNotFoundError();
if (differencesUrl !== undefined)
result.differences = differencesUrl;
this.logger.debug(this.LOG_COMPONENT, 'Memberships retrieved');
return result;
}
async fetchPage(pageUrl, query, accessToken) {
const headers = {
authorization: (0, authorization_header_1.buildBearerAuthorization)(accessToken),
accept: this.NRPS_ACCEPT,
};
const response = await this.requestHandler.get(pageUrl, { query, headers });
const memberships = (0, validation_1.validate)(names_and_roles_schemas_1.MembershipContainerSchema, response.data);
const parsedLinks = (0, link_header_1.parseLinkHeader)(response.headers.link);
return {
memberships,
next: parsedLinks?.next?.url,
differences: parsedLinks?.differences?.url,
};
}
}
exports.NamesAndRoles = NamesAndRoles;
exports.default = NamesAndRoles;
//# sourceMappingURL=names-and-roles.service.js.map