ltijs
Version:
Easily turn your web application into a LTI 1.3 Learning Tool.
117 lines • 6.13 kB
JavaScript
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.DeepLinking = void 0;
const node_path_1 = __importDefault(require("node:path"));
const id_token_constants_1 = require("#services/launch/id-token.constants");
const jwt_1 = require("#utils/crypto/jwt");
const jwt_constants_1 = require("#utils/crypto/jwt.constants");
const random_1 = require("#utils/random/random");
const template_renderer_1 = require("#utils/templating/template-renderer");
const html_escape_1 = require("#utils/templating/html-escape");
const validation_1 = require("#utils/validation/validation");
const deep_linking_schemas_1 = require("#services/deep-linking/deep-linking.schemas");
const errors_1 = require("#services/deep-linking/errors");
const platform_keys_1 = require("#services/platform-manager/platform-keys");
class DeepLinking {
LOG_COMPONENT = 'deepLinkingService';
RESPONSE_TTL_SECONDS = 60;
RESPONSE_MESSAGE_TYPE = 'LtiDeepLinkingResponse';
DEEP_LINKING_SUBMISSION_FORM_TEMPLATE = node_path_1.default.join(__dirname, 'templates', 'deep-linking-submission-form.spy');
CONTENT_ITEMS_CLAIM = 'https://purl.imsglobal.org/spec/lti-dl/claim/content_items';
MSG_CLAIM = 'https://purl.imsglobal.org/spec/lti-dl/claim/msg';
ERROR_MSG_CLAIM = 'https://purl.imsglobal.org/spec/lti-dl/claim/errormsg';
LOG_CLAIM = 'https://purl.imsglobal.org/spec/lti-dl/claim/log';
ERROR_LOG_CLAIM = 'https://purl.imsglobal.org/spec/lti-dl/claim/errorlog';
DATA_CLAIM = 'https://purl.imsglobal.org/spec/lti-dl/claim/data';
launchContext;
logger;
constructor(launchContext, logger) {
this.launchContext = launchContext;
this.logger = logger;
}
/** Whether this launch is a deep-linking launch. Check before calling any other method on this service. */
isAvailable() {
return this.launchContext.idToken.services.deepLinking.available;
}
async createDeepLinkingMessage(contentItems, options) {
this.ensureServiceAvailability();
const { platform, rawIdToken: idToken } = this.launchContext;
const settings = this.resolveDeepLinkingSettings(idToken);
return await this.signDeepLinkingMessage({ idToken, platform, settings, contentItems, options });
}
async createDeepLinkingForm(contentItems, options) {
this.ensureServiceAvailability();
const { platform, rawIdToken: idToken } = this.launchContext;
const settings = this.resolveDeepLinkingSettings(idToken);
const message = await this.signDeepLinkingMessage({ idToken, platform, settings, contentItems, options });
return (0, template_renderer_1.renderTemplate)(this.DEEP_LINKING_SUBMISSION_FORM_TEMPLATE, {
action: (0, html_escape_1.escapeHtmlAttribute)(settings.deep_link_return_url ?? ''),
message,
});
}
async signDeepLinkingMessage(request) {
const { idToken, platform, settings, contentItems, options } = request;
const items = (0, validation_1.validate)(deep_linking_schemas_1.ContentItemsInputSchema, contentItems);
const accepted = this.filterContentItems(items, settings);
const payload = {
iss: platform.clientId,
aud: idToken.iss,
nonce: (0, random_1.randomJti)(),
[id_token_constants_1.IdTokenClaim.DeploymentId]: idToken[id_token_constants_1.IdTokenClaim.DeploymentId],
[id_token_constants_1.IdTokenClaim.MessageType]: this.RESPONSE_MESSAGE_TYPE,
[id_token_constants_1.IdTokenClaim.Version]: idToken[id_token_constants_1.IdTokenClaim.Version],
[this.CONTENT_ITEMS_CLAIM]: accepted,
};
const errMessage = options?.errMessage ?? options?.errmessage;
const errLog = options?.errLog ?? options?.errlog;
if (options?.message !== undefined)
payload[this.MSG_CLAIM] = options.message;
if (errMessage !== undefined)
payload[this.ERROR_MSG_CLAIM] = errMessage;
if (options?.log !== undefined)
payload[this.LOG_CLAIM] = options.log;
if (errLog !== undefined)
payload[this.ERROR_LOG_CLAIM] = errLog;
if (settings.data !== undefined)
payload[this.DATA_CLAIM] = settings.data;
this.logger.debug(this.LOG_COMPONENT, `Signing deep linking response with ${accepted.length} content item(s)`);
return (0, jwt_1.signJwt)(payload, (0, platform_keys_1.resolvePlatformPrivateKey)(platform), {
algorithm: jwt_constants_1.RS256_ALGORITHM,
expiresIn: this.RESPONSE_TTL_SECONDS,
keyid: platform.id,
});
}
ensureServiceAvailability() {
if (!this.isAvailable())
throw new errors_1.DeepLinkingNotAvailableError();
}
resolveDeepLinkingSettings(idToken) {
const settings = idToken[id_token_constants_1.IdTokenClaim.DeepLinkingSettings];
if (settings === undefined)
throw new errors_1.MissingDeepLinkSettingsError();
return settings;
}
filterContentItems(items, settings) {
const acceptedTypes = settings.accept_types ?? [];
// `accept_multiple` arrives from an external, platform-controlled id_token claim; the declared
// `boolean` type isn't a runtime guarantee. Some platforms send the string 'false' instead of the
// boolean, so both must be treated the same way here, not just the boolean.
const rawAcceptMultiple = settings.accept_multiple;
const acceptMultiple = rawAcceptMultiple !== false && rawAcceptMultiple !== 'false';
const accepted = [];
for (const item of items) {
if (!acceptedTypes.includes(item.type))
continue;
accepted.push(item);
if (!acceptMultiple)
break;
}
return accepted;
}
}
exports.DeepLinking = DeepLinking;
exports.default = DeepLinking;
//# sourceMappingURL=deep-linking.service.js.map