Source

session-stores/adaptive.js

import { computed } from '@ember/object';
import { inject as service } from '@ember/service';
import { getOwner } from '@ember/application';
import Base from './base';

const LOCAL_STORAGE_TEST_KEY = '_ember_simple_auth_test_key';

const proxyToInternalStore = function() {
  return computed({
    get(key) {
      return this.get(`_${key}`);
    },
    set(key, value) {
      this.set(`_${key}`, value);
      let _store = this.get('_store');
      if (_store) {
        _store.set(key, value);
      }
      return value;
    }
  });
};

/**
  Session store that persists data in the browser's `localStorage` (see
  {@linkplain LocalStorageStore}) if that is available or in
  a cookie (see {@linkplain CookieStore}) if it is not.

  __This is the default store that Ember Simple Auth will use when the
  application doesn't define a custom store.__

  __This session store does not work with FastBoot. In order to use Ember
  Simple Auth with FastBoot, configure the
  {@linkplain CookieStore} as the application's session
  store.__

  @class AdaptiveStore
  @extends BaseStore
  @public
*/
export default Base.extend({
  /**
    The `localStorage` key the store persists data in if `localStorage` is
    available.

    @memberof AdaptiveStore
    @property localStorageKey
    @type String
    @default 'ember_simple_auth-session'
    @public
  */
  localStorageKey: 'ember_simple_auth-session',

  /**
    The domain to use for the cookie if `localStorage` is not available, e.g.,
    "example.com", ".example.com" (which includes all subdomains) or
    "subdomain.example.com". If not explicitly set, the cookie domain defaults
    to the domain the session was authenticated on.

    @memberof AdaptiveStore
    @property cookieDomain
    @type String
    @default null
    @public
  */
  _cookieDomain: null,
  cookieDomain: proxyToInternalStore(),

  /**
    Allows servers to assert that a cookie ought not to be sent along with
    cross-site requests, which provides some protection against cross-site
    request forgery attacks (CSRF).

    Available options:
    - "Strict"
    - "Lax"

    @memberof AdaptiveStore
    @property sameSite
    @type String
    @default null
    @public
  */
  _sameSite: null,
  sameSite: proxyToInternalStore(),

  /**
    The name of the cookie to use if `localStorage` is not available.

    @memberof AdaptiveStore
    @property cookieName
    @type String
    @default ember_simple_auth-session
    @public
  */
  _cookieName: 'ember_simple_auth-session',
  cookieName: proxyToInternalStore(),

  /**
    The path to use for the cookie, e.g., "/", "/something".

    @memberof AdaptiveStore
    @property cookiePath
    @type String
    @default '/'
    @public
  */
  _cookiePath: '/',
  cookiePath: proxyToInternalStore(),

  /**
    The expiration time for the cookie in seconds if `localStorage` is not
    available. A value of `null` will make the cookie a session cookie that
    expires and gets deleted when the browser is closed.

    @memberof AdaptiveStore
    @property cookieExpirationTime
    @default null
    @type Integer
    @public
  */
  _cookieExpirationTime: null,
  cookieExpirationTime: proxyToInternalStore(),

  _cookies: service('cookies'),

  _isLocalStorageAvailable: computed({
    get() {
      return testLocalStorageAvailable();
    },

    set(key, value) {
      return value;
    }
  }),

  init() {
    this._super(...arguments);

    let owner = getOwner(this);
    if (owner && !this.hasOwnProperty('_fastboot')) {
      this._fastboot = owner.lookup('service:fastboot');
    }

    let store;
    if (this.get('_isLocalStorageAvailable')) {
      const localStorage = owner.lookup('session-store:local-storage');
      const options = { key: this.get('localStorageKey') };

      options._isFastBoot = false;
      localStorage.setProperties(options);

      store = localStorage;
    } else {
      const cookieStorage = owner.lookup('session-store:cookie');
      const options = this.getProperties('cookieDomain', 'cookieName', 'cookieExpirationTime', 'cookiePath', 'sameSite');

      cookieStorage.setProperties(options);
      this.set('cookieExpirationTime', cookieStorage.get('cookieExpirationTime'));

      store = cookieStorage;
    }

    this.set('_store', store);
    this._setupStoreEvents(store);
  },

  _setupStoreEvents(store) {
    store.on('sessionDataUpdated', (data) => {
      this.trigger('sessionDataUpdated', data);
    });
    return store;
  },

  /**
    Persists the `data` in the `localStorage` if it is available or in a cookie
    if it is not.

    @memberof AdaptiveStore
    @method persist
    @param {Object} data The data to persist
    @return {Ember.RSVP.Promise} A promise that resolves when the data has successfully been persisted and rejects otherwise.
    @public
  */
  persist() {
    return this.get('_store').persist(...arguments);
  },

  /**
    Returns all data currently stored in the `localStorage` if that is
    available - or if it is not, in the cookie - as a plain object.

    @memberof AdaptiveStore
    @method restore
    @return {Ember.RSVP.Promise} A promise that resolves with the data currently persisted in the store when the data has been restored successfully and rejects otherwise.
    @public
  */
  restore() {
    return this.get('_store').restore();
  },

  /**
    Clears the store by deleting the
    {@linkplain LocalStorageStore.key} from
    `localStorage` if that is available or by deleting the cookie if it is not.

    @memberof AdaptiveStore
    @method clear
    @return {Ember.RSVP.Promise} A promise that resolves when the store has been cleared successfully and rejects otherwise.
    @public
  */
  clear() {
    return this.get('_store').clear();
  }
});

function testLocalStorageAvailable() {
  try {
    localStorage.setItem(LOCAL_STORAGE_TEST_KEY, true);
    localStorage.removeItem(LOCAL_STORAGE_TEST_KEY);
    return true;
  } catch (e) {
    return false;
  }
}