UNPKG

ltijs

Version:

Easily turn your web application into a LTI 1.3 Learning Tool.

117 lines • 6.13 kB
"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