Manual Reference Source Test

src/modules/identity-native.js

import Observable from './observable';

/**
 * Identity native controller
 *
 * Allows consumers to send and receive messages via a native application bridge.
 * The native application must be set up to create a nativeWrapper window attribute
 * to leverage the bridge calls.
 */
class IdentityNative extends Observable {
  /**
   * IdentityNative constructor
   */
  constructor() {
    super();

    Object.assign(this, {
      _commandIdCounter: 1,
      _wrapperReady: false,
    });

    this._wrapperPromise = new Promise((resolve, reject) => {
      // Reject after timeout
      const timeout = setTimeout(reject, 5000);

      this.on('nativeWrapperIsReady', (properties) => {
        if (properties && properties.ready) {
          this._wrapperReady = true;
          clearTimeout(timeout);
          return resolve(true);
        }
      });
    }).then(this.onWrapper.bind(this));

    this._wrapper = window.nativeWrapper;

    if (this._wrapper && this._wrapper.sendMessage) {
      this.wrapperReady();
      this.sendMessage({type: 'Event', subject: 'AppReady'});
    } else {
      this._wrapper = window.nativeWrapper = {};
    }

    // Make sure previous onMessage functions aren't overwritten
    if (typeof this._wrapper.onMessage === 'function') {
      this._previousOnMessage = this._wrapper.onMessage;
    }

    this._wrapper.onMessage = this.onMessage.bind(this);
  }

  /**
   * Return if bridge is initialized or not
   * @return {boolean}
   */
  get initialized() {
    return this._initialized;
  }

  /**
   * Initialize the native controller
   *
   * @return {Promise}
   */
  init() {
    return new Promise((resolve, reject) => {
      this._wrapperPromise.then(resolve, reject);
    });
  }

  /**
   * Add callbacks to the onMessage queue
   * @param {function} callback
   * @param {string} type
   */
  subscribe(callback, type = 'CommandResult') {
    if (!this._onMessage) this._onMessage = [];
    this._onMessage.push({type, callback});
  }

  /**
   * Listen for messages from the native wrapper and add to the event bus
   *
   * @param {Object} message
   */
  onMessage(message) {
    // Existing onMessage callback
    if (this._previousOnMessage) this._previousOnMessage(message);

    const messageType = message.type || 'Event';
    const subject = message.subject;

    if (!this._wrapperReady && subject === 'BridgeReady') {
      this.wrapperReady();
    }

    if (this._onMessage && this._onMessage.length > 0) {
      this._onMessage.forEach(({type, callback}) => {
        if (type === messageType) callback(subject, message);
      });
    }
  }

  /**
   * Initialize the native wrapper
   *
   * @return {Object}
   */
  onWrapper() {
    this._wrapper = window.nativeWrapper;
    return window.nativeWrapper;
  }

  /**
   * Notify that the wrapper is ready
   */
  wrapperReady() {
    this.notify('nativeWrapperIsReady', {ready: true});
  }

  /**
   * Send a message to the native wrapper
   *
   * @param {Object} message
   * @return {Promise}
   */
  sendMessage(message) {
    return this._wrapperPromise.then((wrapper) => wrapper.sendMessage(message));
  }

  /**
   * Send a command to the native wrapper
   *
   * @param {string} subject
   * @param {Object} payload
   */
  sendCommand(subject, payload = {}) {
    const commandId = this._commandIdCounter++;

    this.sendMessage({
      type: 'Command',
      subject: subject,
      commandId: commandId,
      payload: payload,
    });
  }
}

export default IdentityNative;