@atomicjolt/lti-client
Version:
Client Javascript libraries to handle LTI.
91 lines • 3.95 kB
JavaScript
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