'use strict';

Object.defineProperty(exports, "__esModule", {
  value: true
});
exports.TokenApiService = exports.TOKEN_STORAGE_KEY = exports.CALL_TOKEN_API = undefined;

var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();

exports.storeToken = storeToken;
exports.retrieveToken = retrieveToken;
exports.removeToken = removeToken;
exports.checkTokenFreshness = checkTokenFreshness;
exports.shouldRequestNewToken = shouldRequestNewToken;
exports.actionAsPromise = actionAsPromise;
exports.createTokenApiMiddleware = createTokenApiMiddleware;

var _lodash = require('lodash');

var _lodash2 = _interopRequireDefault(_lodash);

var _jsonwebtoken = require('jsonwebtoken');

var _jsonwebtoken2 = _interopRequireDefault(_jsonwebtoken);

var _moment = require('moment');

var _moment2 = _interopRequireDefault(_moment);

var _isomorphicFetch = require('isomorphic-fetch');

var _isomorphicFetch2 = _interopRequireDefault(_isomorphicFetch);

function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }

function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }

var CALL_TOKEN_API = exports.CALL_TOKEN_API = Symbol('Call API');
var TOKEN_STORAGE_KEY = exports.TOKEN_STORAGE_KEY = 'reduxMiddlewareAuthToken';

var MIN_TOKEN_LIFESPAN = 300;

var NotImplemented = function NotImplemented(message) {
  this.name = 'NotImplemented';
  this.message = message || 'Method not implemented';
  this.stack = new Error().stack;
};
NotImplemented.prototype = Object.create(Error.prototype);
NotImplemented.prototype.constructor = NotImplemented;

function checkResponseIsOk(response) {
  return response.ok ? response : response.text().then(function (text) {
    return Promise.reject(text);
  });
}

function responseToCompletion(response) {
  var contentType = response.headers.get('Content-Type');
  if (contentType && _lodash2.default.startsWith(contentType, 'application/json')) {
    return response.json();
  }
  return response.text();
}

function createAsyncAction(type, step, payload) {
  var action = {
    type: type + '_' + step,
    payload: payload,
    meta: {
      asyncStep: step
    }
  };
  if (payload && payload instanceof Error) {
    Object.assign(action.meta, {
      error: true
    });
  }
  return action;
}

function createStartAction(type, payload) {
  return createAsyncAction(type, 'START', payload);
}

function createCompletionAction(type, payload) {
  return createAsyncAction(type, 'COMPLETED', payload);
}

function createFailureAction(type, error) {
  return createAsyncAction(type, 'FAILED', new TypeError(error));
}

function storeToken(key, response) {
  var token = response.token;
  localStorage.setItem(key, JSON.stringify(token));
  return token;
}

function retrieveToken(key) {
  var storedValue = localStorage.getItem(key);
  if (!storedValue) {
    return null;
  }
  try {
    return JSON.parse(storedValue);
  } catch (e) {
    if (e instanceof SyntaxError) {
      return null;
    }
    throw e;
  }
}

function removeToken(key) {
  localStorage.removeItem(key);
}

function checkTokenFreshness(token) {
  var tokenPayload = _jsonwebtoken2.default.decode(token);
  var expiry = _moment2.default.unix(tokenPayload.exp);
  return expiry.diff((0, _moment2.default)(), 'seconds') > MIN_TOKEN_LIFESPAN;
}

function shouldRequestNewToken() {
  var token = retrieveToken();
  return token ? checkTokenFreshness(token) : false;
}

var TokenApiService = exports.TokenApiService = function () {
  function TokenApiService(apiAction, dispatch) {
    var config = arguments.length <= 2 || arguments[2] === undefined ? {} : arguments[2];

    _classCallCheck(this, TokenApiService);

    this.apiAction = apiAction;
    this.meta = this.apiAction.meta || {};
    console.log('dispatch', dispatch);
    this.dispatch = dispatch;
    this.config = config;
    // config or default values
    this.checkTokenFreshness = this.configOrDefault('checkTokenFreshness');
    this.retrieveToken = this.configOrDefault('retrieveToken');
    this.shouldRequestNewToken = this.configOrDefault('shouldRequestNewToken');
    this.storeToken = this.configOrDefault('storeToken');
    this.addTokenToRequest = this.configOrDefault('addTokenToRequest');
    this.refreshAction = this.configOrNotImplemented('refreshAction');
    this.tokenStorageKey = this.config.tokenStorageKey || TOKEN_STORAGE_KEY;
    this.minTokenLifespan = this.config.minTokenLifespan || MIN_TOKEN_LIFESPAN;

    // bind where needed
    this.storeToken = this.storeToken.bind(this, this.tokenStorageKey);
    this.retrieveToken = this.retrieveToken.bind(this, this.tokenStorageKey);
    // this.failureAction = this.configOrNotImplemented('failureAction');
  }

  _createClass(TokenApiService, [{
    key: 'configOrDefault',
    value: function configOrDefault(key) {
      return this.config[key] || this.defaultMethods[key];
    }
  }, {
    key: 'configOrNotImplemented',
    value: function configOrNotImplemented(key) {
      var method = this.config[key];
      if (!method) {
        throw new NotImplemented('Please provide ' + key + ' in config');
      }
      return method;
    }
  }, {
    key: 'completeApiRequest',
    value: function completeApiRequest(type, finalResponse) {
      this.dispatch(createCompletionAction(type, finalResponse));
    }
  }, {
    key: 'catchApiRequestError',
    value: function catchApiRequestError(type, error) {
      this.dispatch(createFailureAction(type, error));
    }
  }, {
    key: 'apiRequest',
    value: function apiRequest(fetchArgs, action) {
      var meta = action.meta || {};
      var completeApiRequest = this.completeApiRequest.bind(this, action.type);
      var catchApiRequestError = this.catchApiRequestError.bind(this, action.type);
      return _isomorphicFetch2.default.apply(this, fetchArgs).then(checkResponseIsOk).then(responseToCompletion).then(meta.responseHandler).then(completeApiRequest).catch(catchApiRequestError);
    }
  }, {
    key: 'apiRequestPromise',
    value: function apiRequestPromise(fetchArgs) {
      return function () {
        return _isomorphicFetch2.default.apply(null, fetchArgs).then(checkResponseIsOk).then(responseToCompletion);
      };
    }
  }, {
    key: 'apiCallFromAction',
    value: function apiCallFromAction(action) {
      var token = arguments.length <= 1 || arguments[1] === undefined ? null : arguments[1];

      console.log(action, token);
      var apiFetchArgs = this.getApiFetchArgsFromActionPayload(action.payload, token);
      console.log('apiFetchArgs');
      console.log(apiFetchArgs);
      console.log(this);
      this.dispatch(createStartAction(action.type, action.payload));
      this.apiRequest(apiFetchArgs, action, this.store);
    }
  }, {
    key: 'multipleApiCallsFromAction',
    value: function multipleApiCallsFromAction(action) {
      var _this = this;

      var token = arguments.length <= 1 || arguments[1] === undefined ? null : arguments[1];

      var meta = action.meta || {};
      var promises = _lodash2.default.map(action.payload, function (apiAction) {
        var apiFetchArgs = _this.getApiFetchArgsFromActionPayload(apiAction, token);
        return _this.apiRequestPromise(apiFetchArgs)();
      });
      var completeApiRequest = this.completeApiRequest.bind(this, action.type);
      var catchApiRequestError = this.catchApiRequestError.bind(this, action.type);
      this.dispatch(createStartAction(action.type, action.payload));
      Promise.all(promises).then(meta.responseHandler).then(completeApiRequest).catch(catchApiRequestError);
    }
  }, {
    key: 'defaultAddTokenToRequest',
    value: function defaultAddTokenToRequest(headers, endpoint, body, token) {
      return {
        headers: Object.assign({
          Authorization: 'JWT ' + token
        }, headers),
        endpoint: endpoint,
        body: body
      };
    }
  }, {
    key: 'getApiFetchArgsFromActionPayload',
    value: function getApiFetchArgsFromActionPayload(payload) {
      var token = arguments.length <= 1 || arguments[1] === undefined ? null : arguments[1];
      var authenticate = arguments.length <= 2 || arguments[2] === undefined ? true : arguments[2];
      var headers = payload.headers;
      var endpoint = payload.endpoint;
      var method = payload.method;
      var body = payload.body;
      var credentials = payload.credentials;

      if (_lodash2.default.isUndefined(method)) {
        method = 'GET';
      }
      headers = Object.assign({
        'Content-Type': 'application/json'
      }, headers);
      if (token && authenticate) {
        console.log('token & authenticate');

        var _addTokenToRequest = this.addTokenToRequest(headers, endpoint, body, token);

        headers = _addTokenToRequest.headers;
        endpoint = _addTokenToRequest.endpoint;
        body = _addTokenToRequest.body;
      }
      return [endpoint, _lodash2.default.omitBy({ method: method, body: body, credentials: credentials, headers: headers }, _lodash2.default.isUndefined)];
    }
  }, {
    key: 'call',
    value: function call() {
      var _this2 = this;

      if (this.shouldRequestNewToken) {
        var refreshAction = this.refreshAction(this.token);
        var refreshApiAction = refreshAction[CALL_TOKEN_API];
        var refreshApiActionMeta = refreshApiAction.meta || {};
        var refreshArgs = this.getApiFetchArgsFromActionPayload(refreshApiAction.payload, this.token, refreshApiActionMeta.authenticate);
        _isomorphicFetch2.default.apply(null, refreshArgs).then(checkResponseIsOk).then(responseToCompletion).then(this.storeToken).then(function (token) {
          _this2.curriedApiCallMethod(token);
        }).catch(function (error) {
          _this2.dispatch(createFailureAction(_this2.apiAction.type, error));
        });
      } else {
        console.log('no refresh');
        console.log(this.token);
        this.curriedApiCallMethod(this.token);
      }
    }
  }, {
    key: 'defaultMethods',
    get: function get() {
      return {
        checkTokenFreshness: checkTokenFreshness,
        retrieveToken: retrieveToken,
        storeToken: storeToken,
        addTokenToRequest: this.defaultAddTokenToRequest
      };
    }
  }, {
    key: 'apiCallMethod',
    get: function get() {
      return _lodash2.default.isArrayLikeObject(this.apiAction.payload) ? this.multipleApiCallsFromAction : this.apiCallFromAction;
    }
  }, {
    key: 'curriedApiCallMethod',
    get: function get() {
      return this.apiCallMethod.bind(this, this.apiAction);
    }
  }, {
    key: 'token',
    get: function get() {
      return this.retrieveToken();
    }
  }]);

  return TokenApiService;
}();

function actionAsPromise(action, dispatch, config) {
  console.log('actionAsPromise', dispatch);
  var apiAction = action()[CALL_TOKEN_API];
  if (apiAction) {
    var tokenApiService = new TokenApiService(apiAction, dispatch, config);
    return tokenApiService.call();
  }
}

function createTokenApiMiddleware() {
  var config = arguments.length <= 0 || arguments[0] === undefined ? {} : arguments[0];


  console.log('createTokenApiMiddleware');

  return function (store) {
    return function (next) {
      return function (action) {

        var apiAction = action[CALL_TOKEN_API];

        if (apiAction === undefined) {
          return next(action);
        }

        console.log(apiAction.type);

        var tokenApiService = new TokenApiService(apiAction, store.dispatch, config);

        tokenApiService.call();
      };
    };
  };
}