nponiros/sync_client

View on GitHub
src/sync_client.js

Summary

Maintainability
A
0 mins
Test Coverage
const SYNCABLE_PROTOCOL = 'sync_client_protocol';
const defaultSyncOptions = {
  pollInterval: 10000, // Poll every 10 seconds
  credentials: 'omit',
};

export default function initSyncClient({
  Dexie,
  observable,
  syncable,
  sync,
  isOnline,
  onlineStatusChanged,
  cuid,
}) {
  class SyncClient extends Dexie {
    /*
     * dbName: string, name for the database
     * dbVersions: {version: number, stores: Array<Dexie.SchemaDefinition>}
     * https://github.com/dfahlander/Dexie.js/wiki/Version.stores()
     */
    constructor(dbName, dbVersions, opts = {}) {
      super(dbName, { addons: [observable, syncable, ...(opts.addons ? opts.addons : [])] });
      dbVersions.forEach((version) => {
        if (version.upgrader) {
          this.version(version.version).stores(version.stores).upgrade(version.upgrader);
        } else {
          this.version(version.version).stores(version.stores);
        }
      });

      const protocolImplementation = {
        sync,
        partialsThreshold: opts.partialsThreshold,
      };
      Dexie.Syncable.registerSyncProtocol(SYNCABLE_PROTOCOL, protocolImplementation);

      this.options = {};
      this.urls = [];
      this.statusChangeListeners = {};

      this.syncable.on('statusChanged', (status, url) => {
        const cb = this.statusChangeListeners[url];
        if (cb) {
          cb(Dexie.Syncable.StatusTexts[status]);
        }
      });
    }

    static getID() {
      return cuid();
    }

    _connect(url, options) {
      return this.syncable
          .connect(SYNCABLE_PROTOCOL, url, options)
          .catch((e) => { // disconnect when onError is called
            this.disconnect(url);
            throw e;
          });
    }

    /*
     * options:
     *   pollInterval: number -> How often to resync
     */
    connect(url, options) {
      // First call to connect
      // Setup onlineStatusChanged
      // Check isOnline before trying to connect using Dexie.Syncable
      if (this.urls.indexOf(url) === -1) {
        this.options[url] = Object.assign({}, options, defaultSyncOptions);

        return isOnline(url)
            .then((status) => {
              if (status) {
                return this._connect(url, this.options[url])
                    .then(() => {
                      // Make sure we managed to connect before adding URL
                      // and listener
                      this.urls.push(url);

                      onlineStatusChanged(url, (newStatus) => {
                        if (newStatus) {
                          this._connect(url, this.options[url]);
                        } else {
                          this.disconnect(url);
                        }
                      });
                    });
              }
              return Promise.reject(new Error('Is not online'));
            });
      }
      return Promise.resolve();
    }

    disconnect(url) {
      return this.syncable.disconnect(url)
          .then(() => {
            this.urls = this.urls.filter((u) => u !== url);
          });
    }

    removeUrl(url) {
      return this.syncable.delete(url)
          .then(() => {
            this.urls = this.urls.filter((u) => u !== url);
            this.statusChangeListeners[url] = undefined;
          });
    }

    statusChange(url, cb) {
      this.statusChangeListeners[url] = cb;
    }

    /*
     * Returns a Promise<Array<{url, status}>>
     */
    getStatuses() {
      return this.syncable
          .list()
          .then((urls) => {
            const promises = urls.map((url) => this.syncable.getStatus(url));
            return Promise.all(promises).then((statuses) => {
              return urls.map((url, index) => ({
                url,
                status: Dexie.Syncable.StatusTexts[statuses[index]],
              }));
            });
          });
    }

    getStatus(url) {
      return this.syncable
          .getStatus(url)
          .then((status) => {
            return Dexie.Syncable.StatusTexts[status];
          });
    }

    getID() { return SyncClient.getID(); }
  }

  SyncClient.statuses = Object
      .keys(Dexie.Syncable.Statuses)
      .reduce((statuses, statusKey) => Object.assign(statuses, { [statusKey]: statusKey }), {});

  return SyncClient;
}