UNPKG

@atomicjolt/lti-client

Version:

Client Javascript libraries to handle LTI.

91 lines 3.95 kB
import { PostMessageError, PostMessageErrorType } from "./error"; const DEFAULT_OPTIONS = { origin: "*", targetFrame: null, timeout: 2000, }; /** * A client for sending and receiving messages via the postMessage API according the the LTI postMessage specification * https://www.imsglobal.org/spec/lti-cs-pm/v0p1#response-parameters */ export class PostMessageClient { constructor(options) { this.defaultOptions = { ...DEFAULT_OPTIONS, ...options }; } /** Send a request to the LTI platform via the postMessage API and recieve back the platforms response * If the request times out, a PostMessageError with type Timeout will be thrown * If the platform returns an error, a PostMessageError with type ResponseError will be thrown */ async send(payload, options = {}) { const allOptions = { ...this.defaultOptions, ...options, }; const frame = await this.findTargetFrame(payload.subject, allOptions.targetFrame ?? null); return new Promise((resolve, reject) => { const timeout = setTimeout(() => { reject(new PostMessageError(PostMessageErrorType.Timeout, payload)); }, allOptions.timeout); const receiveMessage = (event) => { if (typeof event.data === "object" && event.data.subject === `${payload.subject}.response` && event.data.message_id === payload.message_id && (event.origin === allOptions.origin || (allOptions.origin === "*" && event.origin !== "null"))) { window.removeEventListener("message", receiveMessage); clearTimeout(timeout); if (event.data.error) { reject(new PostMessageError(PostMessageErrorType.ResponseError, payload, event.data)); } else { resolve(event.data); } } }; window.addEventListener("message", receiveMessage); frame.postMessage(payload, { targetOrigin: allOptions.origin, }); }); } /** Retrieve the list of message capabilities that the platform supports */ async getCapabilities() { const response = await this.send({ subject: "lti.capabilities", message_id: "lti-caps" }, { origin: "*", targetFrame: window.parent ?? window.opener }); return response.supported_messages; } /** Gets the configuration for a capability if the platform supports it, null otherwise */ async getCapability(capability) { const capabilities = await this.getCapabilities(); return capabilities.find((c) => c.subject === capability) ?? null; } /** Generate a unique message id for a request */ messageId(subject, ...args) { const random = Math.random().toString(36).substring(2); return `${subject}-${args.join("-")}-${random}`; } async findTargetFrame(subject, target) { if (typeof target !== "string" && target !== null) return target; if (target == null) { // The platform can provide all of the supported capabilities // so we need to check for the lti.get_data capability and that will // tell us the frame to talk to. const cap = await this.getCapability(subject); target = cap?.frame ?? "_parent"; } const parent = window.parent || window.opener; if (target === "_parent") { return parent; } else { return parent.frames[target] || parent; } } } export class PostMessageClientWrapper { constructor(client) { this.client = client ?? new PostMessageClient(); } } PostMessageClientWrapper.MessageTypes = null; //# sourceMappingURL=client.js.map