UNPKG

ltijs

Version:

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

179 lines • 10.4 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.Provider = void 0; const default_logger_service_1 = require("#services/logger/default/default-logger.service"); const fetch_request_handler_service_1 = require("#services/request-handler/fetch/fetch-request-handler.service"); const mongo_database_manager_service_1 = require("#services/database-manager/mongo/mongo-database-manager.service"); const express_http_handler_service_1 = require("#services/http-handler/express/express-http-handler.service"); const mock_cache_manager_service_1 = require("#services/cache-manager/mock/mock-cache-manager.service"); const platform_manager_service_1 = require("#services/platform-manager/platform-manager.service"); const access_token_manager_service_1 = require("#services/access-token-manager/access-token-manager.service"); const oidc_service_1 = require("#services/oidc/oidc.service"); const launch_service_1 = require("#services/launch/launch.service"); const keyset_service_1 = require("#services/keyset/keyset.service"); const dynamic_registration_service_1 = require("#services/dynamic-registration/dynamic-registration.service"); const errors_1 = require("#services/provider/errors"); /** * The library's main entry point. Construct one per application (`new Provider(options)`), wire up * launch handlers, then call {@link Provider.listen} to start listening. Everything else, platform * registration, resuming a launch outside the original request, closing down cleanly, goes through * this instance or the sub-services exposed on it. */ class Provider { DEFAULT_LOGIN_ROUTE = '/lti/login'; DEFAULT_LAUNCH_ROUTE = '/lti/launch'; DEFAULT_KEYSET_ROUTE = '/lti/keys'; DEFAULT_DYNAMIC_REGISTRATION_ROUTE = '/lti/register'; DEFAULT_PORT = 3000; DEFAULT_TOKEN_MAX_AGE_SECONDS = 10; STARTUP_BANNER = ' _ _______ _____ _ _____\n' + ' | | |__ __|_ _| | |/ ____|\n' + ' | | | | | | | | (___ \n' + ' | | | | | | _ | |\\___ \\ \n' + ' | |____| | _| |_| |__| |____) |\n' + ' |______|_| |_____|\\____/|_____/ \n'; /** The active storage backend. Defaults to `MongoDatabaseManager`; override via `ProviderOptions.databaseManager`. */ databaseManager; /** Register, look up, update, and (de)activate platforms. See its own methods for the full CRUD surface. */ platformManager; /** The active HTTP framework adapter. Defaults to `ExpressHttpHandler`; override via `ProviderOptions.httpHandler`. */ httpHandler; /** The active cache backend. Defaults to a no-op; override via `ProviderOptions.cacheManager` (e.g. `RedisCacheManager`). */ cacheManager; /** Only set when `ProviderOptions.dynamicRegistration` was provided at construction. */ dynamicRegistrationService; logger; loginRoute; launchRoute; keysetRoute; dynamicRegistrationRoute; launchService; port; /** * Every dependency (`databaseManager`, `cacheManager`, `httpHandler`, `requestHandler`, `logger`) has a * working default: pass nothing beyond a database connection (or a full `databaseManager`) to get a * runnable provider. Route defaults are `/lti/login`, `/lti/launch`, `/lti/keys`, `/lti/register`. */ constructor(options) { this.logger = options.logger ?? new default_logger_service_1.DefaultLogger(); const requestHandler = options.requestHandler ?? new fetch_request_handler_service_1.FetchRequestHandler(); this.cacheManager = options.cacheManager ?? new mock_cache_manager_service_1.MockCacheManager(); this.databaseManager = options.databaseManager ?? new mongo_database_manager_service_1.MongoDatabaseManager(this.logger, options.database); const serverOptions = { port: this.DEFAULT_PORT, ...options.server }; this.httpHandler = options.httpHandler ?? new express_http_handler_service_1.ExpressHttpHandler(this.logger, serverOptions); this.port = serverOptions.port; this.platformManager = new platform_manager_service_1.PlatformManager(this.databaseManager, this.logger, this.cacheManager); const accessTokenManager = new access_token_manager_service_1.AccessTokenManager(this.databaseManager, requestHandler, this.logger); const oidcService = new oidc_service_1.OidcService(this.databaseManager, requestHandler, this.cacheManager, this.logger, options.tokenMaxAge ?? this.DEFAULT_TOKEN_MAX_AGE_SECONDS); this.loginRoute = options.routes?.loginRoute ?? this.DEFAULT_LOGIN_ROUTE; this.launchRoute = options.routes?.launchRoute ?? this.DEFAULT_LAUNCH_ROUTE; this.keysetRoute = options.routes?.keysetRoute ?? this.DEFAULT_KEYSET_ROUTE; this.launchService = new launch_service_1.LaunchService(oidcService, this.platformManager, accessTokenManager, this.databaseManager, requestHandler, this.httpHandler, this.logger); this.launchService.prepareHttpRoutes({ loginRoute: this.loginRoute, launchRoute: this.launchRoute }); const keysetService = new keyset_service_1.KeysetService(this.platformManager, this.httpHandler, this.cacheManager, this.logger); keysetService.prepareHttpRoutes(this.keysetRoute); const dynamicRegistration = this.setupDynamicRegistration(options, requestHandler); this.dynamicRegistrationRoute = dynamicRegistration?.route; this.dynamicRegistrationService = dynamicRegistration?.service; } /** Called on a standard resource-link launch (the common case: a student/instructor opening the tool from the LMS). */ onResourceLink(handler) { this.launchService.setOnResourceLinkHandler(handler); } /** Alias of `onResourceLink()`, matching legacy's exact method name for anyone migrating from legacy ltijs. */ onConnect(handler) { this.onResourceLink(handler); } /** Called when the platform launches the tool for content selection (the deep-linking message type). */ onDeepLinking(handler) { this.launchService.setOnDeepLinkingHandler(handler); } /** Called on a submission-review launch (an instructor reviewing a learner's graded submission). */ onSubmissionReview(handler) { this.launchService.setOnSubmissionReviewHandler(handler); } /** * Called instead of throwing the default `UnregisteredPlatformError` when a login request arrives * from a platform this tool hasn't registered yet. Must fully send the response itself. */ onUnregisteredPlatform(handler) { this.launchService.setOnUnregisteredPlatformHandler(handler); } /** * Called instead of throwing the default `PlatformNotActivatedError` when a login request arrives * from a platform that's registered but deactivated. Must fully send the response itself. */ onInactivePlatform(handler) { this.launchService.setOnInactivePlatformHandler(handler); } /** * Overrides the default GET handler for the dynamic-registration route. Throws * `DynamicRegistrationNotConfiguredError` if `ProviderOptions.dynamicRegistration` wasn't set. */ onDynamicRegistration(handler) { if (this.dynamicRegistrationService === undefined) throw new errors_1.DynamicRegistrationNotConfiguredError(); this.dynamicRegistrationService.setHandler(handler); } /** * Resumes a previously-issued launch by its ltik: the session-resumption entry point for a * follow-up request (e.g. protecting a custom app route, or submitting a grade from a background * job) using a ltik obtained from an earlier `LaunchContext`. */ async getLaunchContext(ltik) { return await this.launchService.getLaunchContext(ltik); } /** Registers `path` as an additional launch route, dispatching through the same launch handlers. */ registerLtiRoute(path) { this.launchService.registerLaunchRoute(path); } setupDynamicRegistration(options, requestHandler) { if (options.dynamicRegistration === undefined) return undefined; const route = options.routes?.dynamicRegistrationRoute ?? this.DEFAULT_DYNAMIC_REGISTRATION_ROUTE; const service = new dynamic_registration_service_1.DynamicRegistration(options.dynamicRegistration, { appRoute: this.launchRoute, loginRoute: this.loginRoute, keysetRoute: this.keysetRoute }, this.platformManager, requestHandler, this.httpHandler, this.logger); service.prepareHttpRoutes(route); return { route, service }; } /** * Connects the database and cache backends, starts the HTTP listener, and registers a `SIGINT` handler * that calls {@link Provider.close} before exiting. Prints a startup banner unless `options.silent`. * Port and TLS were already given to the `httpHandler` at construction time (see * `ProviderOptions.server`, which only applies to the default `ExpressHttpHandler`; a custom * `httpHandler` is expected to already be fully configured). */ async listen(options = {}) { await this.databaseManager.listen(); await this.cacheManager.listen(); await this.httpHandler.listen(); if (options.silent !== true) this.printStartupBanner(this.port); process.on('SIGINT', () => { void this.close().finally(() => process.exit()); }); } /** @deprecated Alias of {@link Provider.listen}, kept for backwards compatibility. */ async deploy(options = {}) { await this.listen(options); } /** Stops the HTTP listener and closes the database and cache connections, in that order. */ async close() { await this.httpHandler.close(); await this.databaseManager.close(); await this.cacheManager.close(); } printStartupBanner(port) { const message = `LTI Provider is listening on port ${port}!\n\n` + ' LTI provider config: \n' + ` >Login Route: ${this.loginRoute}\n` + ` >Launch Route: ${this.launchRoute}\n` + ` >Keyset Route: ${this.keysetRoute}\n` + ` >Dynamic Registration Route: ${this.dynamicRegistrationRoute ?? '(disabled)'}`; console.log(this.STARTUP_BANNER); console.log(message); } } exports.Provider = Provider; exports.default = Provider; //# sourceMappingURL=provider.service.js.map