@blockprotocol/core
Version:
Implementation of the Block Protocol Core specification for blocks and embedding applications
170 lines • 6.44 kB
JavaScript
import { CoreBlockHandler } from "./core-block-handler.js";
import { CoreEmbedderHandler } from "./core-embedder-handler.js";
/**
* The base class for creating module handlers from.
* - registers the module with the CoreHandler
* - provides methods for registering callbacks and sending messages
*/
export class ModuleHandler {
constructor({ element, callbacks, moduleName, sourceType, }) {
/** the CoreHandler this module is registered with, for passing messages via */
this.coreHandler = null;
/** the element messages are sent via */
this.element = null;
/**
* the core handler is not available until element is set, so we need a queue
*/
this.coreQueue = [];
/**
* If we register callbacks prior to creating the core handler, we want to
* register those on the core handler once it is available, but before we
* call initialize on it, to ensure callbacks which would catch messages sent
* during initialize are registered. To enable that, we have a separate queue.
*/
this.preCoreInitializeQueue = [];
this.moduleName = moduleName;
this.sourceType = sourceType;
if (callbacks) {
this.registerCallbacks(callbacks);
}
if (element) {
this.initialize(element);
}
}
/**
* You only need to use this if you are constructing a module directly.
* You do not need to use it if you're using a React hook or block template.
*
* This initializes a module with the element it will listen for messages on,
* and must be called if the module was constructed without an element.
*/
initialize(element) {
if (!this.element) {
this.registerModule(element);
}
else if (element !== this.element) {
throw new Error("Could not initialize – already initialized with another element");
}
const coreHandler = this.coreHandler;
if (!coreHandler) {
throw new Error("Could not initialize – missing core handler");
}
this.processCoreCallbackQueue(this.preCoreInitializeQueue);
coreHandler.initialize();
this.processCoreQueue();
}
registerModule(element) {
this.checkIfDestroyed();
if (this.element) {
throw new Error("Already registered");
}
this.element = element;
if (this.sourceType === "block") {
this.coreHandler = CoreBlockHandler.registerModule({
element,
module: this,
});
}
else if (this.sourceType === "embedder") {
this.coreHandler = CoreEmbedderHandler.registerModule({
element,
module: this,
});
}
else {
throw new Error(`Provided sourceType '${this.sourceType}' must be one of 'block' or 'embedder'.`);
}
}
/**
* Unregister and clean up the module.
*/
destroy() {
this.coreHandler?.unregisterModule({ module: this });
this.destroyed = true;
}
checkIfDestroyed() {
if (this.destroyed) {
throw new Error("Module has been destroyed. Please construct a new instance.");
}
}
/** Register callbacks with the CoreHandler to handle incoming messages of specific types */
registerCallbacks(callbacks) {
for (const [messageName, callback] of Object.entries(callbacks)) {
this.registerCallback({ messageName, callback });
}
}
/** Remove callbacks with the CoreHandler for incoming messages of specific types */
removeCallbacks(callbacks) {
for (const [messageName, callback] of Object.entries(callbacks)) {
this.removeCallback({ messageName, callback });
}
}
/** Register a callback with the CoreHandler to handle an incoming messages of a specific type */
registerCallback({ messageName, callback, }) {
this.checkIfDestroyed();
this.getRelevantQueueForCallbacks().push((coreHandler) => coreHandler.registerCallback({
callback,
messageName,
moduleName: this.moduleName,
}));
this.processCoreQueue();
}
/**
* When adding/removing callbacks before calling the core handler is
* available, we want to queue these in a queue which will be processed before
* calling initializing once we create the core handler, to ensure callbacks
* are properly set up before calling initialize
*/
getRelevantQueueForCallbacks() {
return this.coreHandler ? this.coreQueue : this.preCoreInitializeQueue;
}
/** Remove a callback from the CoreHandler for an incoming messages of a specific type */
removeCallback({ messageName, callback, }) {
this.checkIfDestroyed();
this.getRelevantQueueForCallbacks().push((coreHandler) => coreHandler.removeCallback({
callback,
messageName,
moduleName: this.moduleName,
}));
this.processCoreQueue();
}
processCoreQueue() {
this.processCoreCallbackQueue(this.coreQueue);
}
processCoreCallbackQueue(queue) {
const coreHandler = this.coreHandler;
if (coreHandler) {
while (queue.length) {
const callback = queue.shift();
if (callback) {
callback(coreHandler);
}
}
}
}
/** Send a message via the CoreHandler */
sendMessage(args) {
this.checkIfDestroyed();
const { message } = args;
if ("respondedToBy" in args) {
return new Promise((resolve, reject) => {
this.coreQueue.push((coreHandler) => {
coreHandler
.sendMessage({
partialMessage: message,
respondedToBy: args.respondedToBy,
sender: this,
})
.then(resolve, reject);
});
this.processCoreQueue();
});
}
this.coreQueue.push((coreHandler) => coreHandler.sendMessage({
partialMessage: message,
sender: this,
}));
this.processCoreQueue();
}
}
//# sourceMappingURL=module-handler.js.map