chromium/chrome/browser/resources/chromeos/accessibility/common/bridge_helper.ts

// Copyright 2022 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

/**
 * @fileoverview A collection of functions and behaviors helpful for message
 * passing between renderers.
 */

type MessageSender = chrome.runtime.MessageSender;
type TargetHandlers = Record<string, Function>;

/** Targets should be constants, defined in a central place. */
export type TargetType = string;
/** Actions should be constants, defined in a central place. */
export type ActionType = string;

export class BridgeHelper {
  /**
   * This function should only be used by Bridges (e.g. BackgroundBridge,
   * PanelBridge) and not called directly by other classes.
   *
   * @param target The name of the class that will handle this request.
   * @param action The name of the intended function or, if not a direct method of the class,
   *     a pseudo-function name.
   *
   * Any arguments to be passed through can be appended to the function. They
   *     must be convertible to JSON objects for message passing - i.e., no
   *     functions or type information will be retained, and no direct
   *     modification of the original context is possible.
   *
   * @return A promise, that resolves when the handler function has finished and
   *     contains any value returned by the handler.
   */
  static sendMessage(target: TargetType, action: ActionType, ...args: any[]):
      Promise<any> {
    return new Promise(
        resolve => chrome.runtime.sendMessage(
            undefined, {target, action, args}, undefined, resolve));
  }

  static clearAllHandlersForTarget(target: TargetType): void {
    handlers[target] = {};
  }

  /**
   * @param target The name of the class that is registering the handler.
   * @param action The name of the intended function or, if not a direct method
   *     of the class, a pseudo-function name.
   * @param handler A function that performs the indicated action. It may
   *     optionally take parameters, and may have an optional return value
   *     that will be forwarded to the requestor.
   */
  static registerHandler(
      target: TargetType, action: ActionType, handler: Function): void {
    if (!target || !action) {
      return;
    }
    if (!handlers[target]) {
      handlers[target] = {};
    }

    if (handlers[target][action]) {
      throw new Error(
          `Re-assigning handlers for ${target}.${action} is not permitted`);
    }

    handlers[target][action] = handler;
  }
}

// Local to module.

const handlers: Record<TargetType, TargetHandlers> = {};

chrome.runtime.onMessage.addListener(
    (message: any, _sender: MessageSender, respond: (value: any) => void) => {
      const targetHandlers = handlers[message.target];
      if (!targetHandlers || !targetHandlers[message.action]) {
        return false;
      }

      const handler = targetHandlers[message.action];
      Promise.resolve(handler(...message.args)).then(respond);
      return true; /** Wait for asynchronous response. */
    });