"use strict";

Object.defineProperty(exports, "__esModule", {
  value: true
});
exports.default = void 0;
var _intersection2 = _interopRequireDefault(require("lodash/intersection"));
var _pick2 = _interopRequireDefault(require("lodash/pick"));
var _get2 = _interopRequireDefault(require("lodash/get"));
var _gotrueJs = _interopRequireDefault(require("gotrue-js"));
var _jwtDecode = _interopRequireDefault(require("jwt-decode"));
var _ini = _interopRequireDefault(require("ini"));
var _decapCmsLibUtil = require("decap-cms-lib-util");
var _decapCmsBackendGithub = require("decap-cms-backend-github");
var _decapCmsBackendGitlab = require("decap-cms-backend-gitlab");
var _decapCmsBackendBitbucket = require("decap-cms-backend-bitbucket");
var _GitHubAPI = _interopRequireDefault(require("./GitHubAPI"));
var _GitLabAPI = _interopRequireDefault(require("./GitLabAPI"));
var _AuthenticationPage = _interopRequireDefault(require("./AuthenticationPage"));
var _netlifyLfsClient = require("./netlify-lfs-client");
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
function _defineProperty(obj, key, value) { key = _toPropertyKey(key); if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == typeof i ? i : String(i); }
function _toPrimitive(t, r) { if ("object" != typeof t || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != typeof i) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); }
const STATUS_PAGE = 'https://www.netlifystatus.com';
const GIT_GATEWAY_STATUS_ENDPOINT = `${STATUS_PAGE}/api/v2/components.json`;
const GIT_GATEWAY_OPERATIONAL_UNITS = ['Git Gateway'];
const localHosts = {
  localhost: true,
  '127.0.0.1': true,
  '0.0.0.0': true
};
const defaults = {
  identity: '/.netlify/identity',
  gateway: '/.netlify/git',
  largeMedia: '/.netlify/large-media'
};
function getEndpoint(endpoint, netlifySiteURL) {
  if (localHosts[document.location.host.split(':').shift()] && netlifySiteURL && endpoint.match(/^\/\.netlify\//)) {
    const parts = [];
    if (netlifySiteURL) {
      parts.push(netlifySiteURL);
      if (!netlifySiteURL.match(/\/$/)) {
        parts.push('/');
      }
    }
    parts.push(endpoint.replace(/^\//, ''));
    return parts.join('');
  }
  return endpoint;
}

// wait for identity widget to initialize
// force init on timeout
let initPromise = Promise.resolve();
if (window.netlifyIdentity) {
  let initialized = false;
  initPromise = Promise.race([new Promise(resolve => {
    var _window$netlifyIdenti;
    (_window$netlifyIdenti = window.netlifyIdentity) === null || _window$netlifyIdenti === void 0 ? void 0 : _window$netlifyIdenti.on('init', () => {
      initialized = true;
      resolve();
    });
  }), new Promise(resolve => setTimeout(resolve, 2500)).then(() => {
    if (!initialized) {
      var _window$netlifyIdenti2;
      console.log('Manually initializing identity widget');
      (_window$netlifyIdenti2 = window.netlifyIdentity) === null || _window$netlifyIdenti2 === void 0 ? void 0 : _window$netlifyIdenti2.init();
    }
  })]);
}
async function apiGet(path) {
  const apiRoot = 'https://api.netlify.com/api/v1/sites';
  const response = await fetch(`${apiRoot}/${path}`).then(res => res.json());
  return response;
}
class GitGateway {
  constructor(config, options = {}) {
    var _config$backend$branc;
    _defineProperty(this, "config", void 0);
    _defineProperty(this, "api", void 0);
    _defineProperty(this, "branch", void 0);
    _defineProperty(this, "squashMerges", void 0);
    _defineProperty(this, "cmsLabelPrefix", void 0);
    _defineProperty(this, "mediaFolder", void 0);
    _defineProperty(this, "transformImages", void 0);
    _defineProperty(this, "gatewayUrl", void 0);
    _defineProperty(this, "netlifyLargeMediaURL", void 0);
    _defineProperty(this, "backendType", void 0);
    _defineProperty(this, "apiUrl", void 0);
    _defineProperty(this, "authClient", void 0);
    _defineProperty(this, "backend", void 0);
    _defineProperty(this, "acceptRoles", void 0);
    _defineProperty(this, "tokenPromise", void 0);
    _defineProperty(this, "_largeMediaClientPromise", void 0);
    _defineProperty(this, "options", void 0);
    _defineProperty(this, "requestFunction", req => this.tokenPromise().then(token => _decapCmsLibUtil.unsentRequest.withHeaders({
      Authorization: `Bearer ${token}`
    }, req)).then(_decapCmsLibUtil.unsentRequest.performRequest));
    this.options = _objectSpread({
      proxied: true,
      API: null,
      initialWorkflowStatus: ''
    }, options);
    this.config = config;
    this.branch = ((_config$backend$branc = config.backend.branch) === null || _config$backend$branc === void 0 ? void 0 : _config$backend$branc.trim()) || 'master';
    this.squashMerges = config.backend.squash_merges || false;
    this.cmsLabelPrefix = config.backend.cms_label_prefix || '';
    this.mediaFolder = config.media_folder;
    const {
      use_large_media_transforms_in_media_library: transformImages = true
    } = config.backend;
    this.transformImages = transformImages;
    const netlifySiteURL = localStorage.getItem('netlifySiteURL');
    this.apiUrl = getEndpoint(config.backend.identity_url || defaults.identity, netlifySiteURL);
    this.gatewayUrl = getEndpoint(config.backend.gateway_url || defaults.gateway, netlifySiteURL);
    this.netlifyLargeMediaURL = getEndpoint(config.backend.large_media_url || defaults.largeMedia, netlifySiteURL);
    const backendTypeRegex = /\/(github|gitlab|bitbucket)\/?$/;
    const backendTypeMatches = this.gatewayUrl.match(backendTypeRegex);
    if (backendTypeMatches) {
      this.backendType = backendTypeMatches[1];
      this.gatewayUrl = this.gatewayUrl.replace(backendTypeRegex, '');
    } else {
      this.backendType = null;
    }
    this.backend = null;
    _AuthenticationPage.default.authClient = () => this.getAuthClient();
  }
  isGitBackend() {
    return true;
  }
  async status() {
    const api = await fetch(GIT_GATEWAY_STATUS_ENDPOINT).then(res => res.json()).then(res => {
      return res['components'].filter(statusComponent => GIT_GATEWAY_OPERATIONAL_UNITS.includes(statusComponent.name)).every(statusComponent => statusComponent.status === 'operational');
    }).catch(e => {
      console.warn('Failed getting Git Gateway status', e);
      return true;
    });
    let auth = false;
    // no need to check auth if api is down
    if (api) {
      var _this$tokenPromise;
      auth = (await ((_this$tokenPromise = this.tokenPromise) === null || _this$tokenPromise === void 0 ? void 0 : _this$tokenPromise.call(this).then(token => !!token).catch(e => {
        console.warn('Failed getting Identity token', e);
        return false;
      }))) || false;
    }
    return {
      auth: {
        status: auth
      },
      api: {
        status: api,
        statusPage: STATUS_PAGE
      }
    };
  }
  async getAuthClient() {
    if (this.authClient) {
      return this.authClient;
    }
    await initPromise;
    if (window.netlifyIdentity) {
      this.authClient = {
        logout: () => {
          var _window$netlifyIdenti3;
          return (_window$netlifyIdenti3 = window.netlifyIdentity) === null || _window$netlifyIdenti3 === void 0 ? void 0 : _window$netlifyIdenti3.logout();
        },
        currentUser: () => {
          var _window$netlifyIdenti4;
          return (_window$netlifyIdenti4 = window.netlifyIdentity) === null || _window$netlifyIdenti4 === void 0 ? void 0 : _window$netlifyIdenti4.currentUser();
        },
        clearStore: () => {
          var _window$netlifyIdenti5;
          const store = (_window$netlifyIdenti5 = window.netlifyIdentity) === null || _window$netlifyIdenti5 === void 0 ? void 0 : _window$netlifyIdenti5.store;
          if (store) {
            store.user = null;
            store.modal.page = 'login';
            store.saving = false;
          }
        }
      };
    } else {
      const goTrue = new _gotrueJs.default({
        APIUrl: this.apiUrl
      });
      this.authClient = {
        logout: () => {
          const user = goTrue.currentUser();
          if (user) {
            return user.logout();
          }
        },
        currentUser: () => goTrue.currentUser(),
        login: goTrue.login.bind(goTrue),
        clearStore: () => undefined
      };
    }
    return this.authClient;
  }
  authenticate(credentials) {
    const user = credentials;
    this.tokenPromise = async () => {
      try {
        const func = user.jwt.bind(user);
        const token = await func();
        return token;
      } catch (error) {
        throw new _decapCmsLibUtil.AccessTokenError(`Failed getting access token: ${error.message}`);
      }
    };
    return this.tokenPromise().then(async token => {
      if (!this.backendType) {
        const {
          github_enabled: githubEnabled,
          gitlab_enabled: gitlabEnabled,
          bitbucket_enabled: bitbucketEnabled,
          roles
        } = await _decapCmsLibUtil.unsentRequest.fetchWithTimeout(`${this.gatewayUrl}/settings`, {
          headers: {
            Authorization: `Bearer ${token}`
          }
        }).then(async res => {
          const contentType = res.headers.get('Content-Type') || '';
          if (!contentType.includes('application/json') && !contentType.includes('text/json')) {
            throw new _decapCmsLibUtil.APIError(`Your Git Gateway backend is not returning valid settings. Please make sure it is enabled.`, res.status, 'Git Gateway');
          }
          const body = await res.json();
          if (!res.ok) {
            throw new _decapCmsLibUtil.APIError(`Git Gateway Error: ${body.message ? body.message : body}`, res.status, 'Git Gateway');
          }
          return body;
        });
        this.acceptRoles = roles;
        if (githubEnabled) {
          this.backendType = 'github';
        } else if (gitlabEnabled) {
          this.backendType = 'gitlab';
        } else if (bitbucketEnabled) {
          this.backendType = 'bitbucket';
        }
      }
      if (this.acceptRoles && this.acceptRoles.length > 0) {
        const userRoles = (0, _get2.default)((0, _jwtDecode.default)(token), 'app_metadata.roles', []);
        const validRole = (0, _intersection2.default)(userRoles, this.acceptRoles).length > 0;
        if (!validRole) {
          throw new Error("You don't have sufficient permissions to access Decap CMS");
        }
      }
      const userData = {
        name: user.user_metadata.full_name || user.email.split('@').shift(),
        email: user.email,
        avatar_url: user.user_metadata.avatar_url,
        metadata: user.user_metadata
      };
      const apiConfig = {
        apiRoot: `${this.gatewayUrl}/${this.backendType}`,
        branch: this.branch,
        tokenPromise: this.tokenPromise,
        commitAuthor: (0, _pick2.default)(userData, ['name', 'email']),
        isLargeMedia: filename => this.isLargeMediaFile(filename),
        squashMerges: this.squashMerges,
        cmsLabelPrefix: this.cmsLabelPrefix,
        initialWorkflowStatus: this.options.initialWorkflowStatus
      };
      if (this.backendType === 'github') {
        this.api = new _GitHubAPI.default(apiConfig);
        this.backend = new _decapCmsBackendGithub.GitHubBackend(this.config, _objectSpread(_objectSpread({}, this.options), {}, {
          API: this.api
        }));
      } else if (this.backendType === 'gitlab') {
        this.api = new _GitLabAPI.default(apiConfig);
        this.backend = new _decapCmsBackendGitlab.GitLabBackend(this.config, _objectSpread(_objectSpread({}, this.options), {}, {
          API: this.api
        }));
      } else if (this.backendType === 'bitbucket') {
        this.api = new _decapCmsBackendBitbucket.API(_objectSpread(_objectSpread({}, apiConfig), {}, {
          requestFunction: this.requestFunction,
          hasWriteAccess: async () => true
        }));
        this.backend = new _decapCmsBackendBitbucket.BitbucketBackend(this.config, _objectSpread(_objectSpread({}, this.options), {}, {
          API: this.api
        }));
      }
      if (!(await this.api.hasWriteAccess())) {
        throw new Error("You don't have sufficient permissions to access Decap CMS");
      }
      return {
        name: userData.name,
        login: userData.email,
        avatar_url: userData.avatar_url
      };
    });
  }
  async restoreUser() {
    const client = await this.getAuthClient();
    const user = client.currentUser();
    if (!user) return Promise.reject();
    return this.authenticate(user);
  }
  authComponent() {
    return _AuthenticationPage.default;
  }
  async logout() {
    const client = await this.getAuthClient();
    try {
      client.logout();
    } catch (e) {
      // due to a bug in the identity widget (gotrue-js actually) the store is not reset if logout fails
      // TODO: remove after https://github.com/netlify/gotrue-js/pull/83 is merged
      client.clearStore();
    }
  }
  getToken() {
    return this.tokenPromise();
  }
  async entriesByFolder(folder, extension, depth) {
    return this.backend.entriesByFolder(folder, extension, depth);
  }
  allEntriesByFolder(folder, extension, depth, pathRegex) {
    return this.backend.allEntriesByFolder(folder, extension, depth, pathRegex);
  }
  entriesByFiles(files) {
    return this.backend.entriesByFiles(files);
  }
  getEntry(path) {
    return this.backend.getEntry(path);
  }
  async unpublishedEntryDataFile(collection, slug, path, id) {
    return this.backend.unpublishedEntryDataFile(collection, slug, path, id);
  }
  async isLargeMediaFile(path) {
    const client = await this.getLargeMediaClient();
    return client.enabled && client.matchPath(path);
  }
  async unpublishedEntryMediaFile(collection, slug, path, id) {
    const isLargeMedia = await this.isLargeMediaFile(path);
    if (isLargeMedia) {
      const branch = this.backend.getBranch(collection, slug);
      const {
        url,
        blob
      } = await this.getLargeMediaDisplayURL({
        path,
        id
      }, branch);
      return {
        id,
        name: (0, _decapCmsLibUtil.basename)(path),
        path,
        url,
        displayURL: url,
        file: new File([blob], (0, _decapCmsLibUtil.basename)(path)),
        size: blob.size
      };
    } else {
      return this.backend.unpublishedEntryMediaFile(collection, slug, path, id);
    }
  }
  getMedia(mediaFolder = this.mediaFolder) {
    return this.backend.getMedia(mediaFolder);
  }

  // this method memoizes this._getLargeMediaClient so that there can
  // only be one client at a time
  getLargeMediaClient() {
    if (this._largeMediaClientPromise) {
      return this._largeMediaClientPromise;
    }
    this._largeMediaClientPromise = this._getLargeMediaClient();
    return this._largeMediaClientPromise;
  }
  _getLargeMediaClient() {
    const netlifyLargeMediaEnabledPromise = this.api.readFile('.lfsconfig').then(config => _ini.default.decode(config)).then(({
      lfs: {
        url
      }
    }) => new URL(url)).then(lfsURL => ({
      enabled: lfsURL.hostname.endsWith('netlify.com') || lfsURL.hostname.endsWith('netlify.app')
    })).catch(err => ({
      enabled: false,
      err
    }));
    const lfsPatternsPromise = this.api.readFile('.gitattributes').then(attributes => (0, _decapCmsLibUtil.getLargeMediaPatternsFromGitAttributesFile)(attributes)).then(patterns => ({
      err: null,
      patterns
    })).catch(err => {
      if (err.message.includes('404')) {
        console.log('This 404 was expected and handled appropriately.');
        return {
          err: null,
          patterns: []
        };
      } else {
        return {
          err,
          patterns: []
        };
      }
    });
    return Promise.all([netlifyLargeMediaEnabledPromise, lfsPatternsPromise]).then(([{
      enabled: maybeEnabled
    }, {
      patterns,
      err: patternsErr
    }]) => {
      const enabled = maybeEnabled && !patternsErr;

      // We expect LFS patterns to exist when the .lfsconfig states
      // that we're using Netlify Large Media
      if (maybeEnabled && patternsErr) {
        console.error(patternsErr);
      }
      return (0, _netlifyLfsClient.getClient)({
        enabled,
        rootURL: this.netlifyLargeMediaURL,
        makeAuthorizedRequest: this.requestFunction,
        patterns,
        transformImages: this.transformImages ? {
          nf_resize: 'fit',
          w: 560,
          h: 320
        } : false
      });
    });
  }
  async getLargeMediaDisplayURL({
    path,
    id
  }, branch = this.branch) {
    const readFile = (path, id, {
      parseText
    }) => this.api.readFile(path, id, {
      branch,
      parseText
    });
    const items = await (0, _decapCmsLibUtil.entriesByFiles)([{
      path,
      id
    }], readFile, this.api.readFileMetadata.bind(this.api), 'Git-Gateway');
    const entry = items[0];
    const pointerFile = (0, _decapCmsLibUtil.parsePointerFile)(entry.data);
    if (!pointerFile.sha) {
      console.warn(`Failed parsing pointer file ${path}`);
      return {
        url: path,
        blob: new Blob()
      };
    }
    const client = await this.getLargeMediaClient();
    const {
      url,
      blob
    } = await client.getDownloadURL(pointerFile);
    return {
      url,
      blob
    };
  }
  async getMediaDisplayURL(displayURL) {
    const {
      path,
      id
    } = displayURL;
    const isLargeMedia = await this.isLargeMediaFile(path);
    if (isLargeMedia) {
      const {
        url
      } = await this.getLargeMediaDisplayURL({
        path,
        id
      });
      return url;
    }
    if (typeof displayURL === 'string') {
      return displayURL;
    }
    const url = await this.backend.getMediaDisplayURL(displayURL);
    return url;
  }
  async getMediaFile(path) {
    const isLargeMedia = await this.isLargeMediaFile(path);
    if (isLargeMedia) {
      const {
        url,
        blob
      } = await this.getLargeMediaDisplayURL({
        path,
        id: null
      });
      return {
        id: url,
        name: (0, _decapCmsLibUtil.basename)(path),
        path,
        url,
        displayURL: url,
        file: new File([blob], (0, _decapCmsLibUtil.basename)(path)),
        size: blob.size
      };
    }
    return this.backend.getMediaFile(path);
  }
  async persistEntry(entry, options) {
    const client = await this.getLargeMediaClient();
    if (client.enabled) {
      const assets = await (0, _decapCmsLibUtil.getLargeMediaFilteredMediaFiles)(client, entry.assets);
      return this.backend.persistEntry(_objectSpread(_objectSpread({}, entry), {}, {
        assets
      }), options);
    } else {
      return this.backend.persistEntry(entry, options);
    }
  }
  async persistMedia(mediaFile, options) {
    const {
      fileObj,
      path
    } = mediaFile;
    const displayURL = fileObj ? URL.createObjectURL(fileObj) : '';
    const client = await this.getLargeMediaClient();
    const fixedPath = path.startsWith('/') ? path.slice(1) : path;
    const isLargeMedia = await this.isLargeMediaFile(fixedPath);
    if (isLargeMedia) {
      const persistMediaArgument = await (0, _decapCmsLibUtil.getPointerFileForMediaFileObj)(client, fileObj, path);
      return _objectSpread(_objectSpread({}, await this.backend.persistMedia(persistMediaArgument, options)), {}, {
        displayURL
      });
    }
    return await this.backend.persistMedia(mediaFile, options);
  }
  deleteFiles(paths, commitMessage) {
    return this.backend.deleteFiles(paths, commitMessage);
  }
  async getDeployPreview(collection, slug) {
    let preview = await this.backend.getDeployPreview(collection, slug);
    if (!preview) {
      try {
        // if the commit doesn't have a status, try to use Netlify API directly
        // this is useful when builds are queue up in Netlify and don't have a commit status yet
        // and only works with public logs at the moment
        // TODO: get Netlify API Token and use it to access private logs
        const siteId = new URL(localStorage.getItem('netlifySiteURL') || '').hostname;
        const site = await apiGet(siteId);
        const deploys = await apiGet(`${site.id}/deploys?per_page=100`);
        if (deploys.length > 0) {
          const ref = await this.api.getUnpublishedEntrySha(collection, slug);
          const deploy = deploys.find(d => d.commit_ref === ref);
          if (deploy) {
            preview = {
              status: deploy.state === 'ready' ? _decapCmsLibUtil.PreviewState.Success : _decapCmsLibUtil.PreviewState.Other,
              url: deploy.deploy_url
            };
          }
        }
        // eslint-disable-next-line no-empty
      } catch (e) {}
    }
    return preview;
  }
  unpublishedEntries() {
    return this.backend.unpublishedEntries();
  }
  unpublishedEntry({
    id,
    collection,
    slug
  }) {
    return this.backend.unpublishedEntry({
      id,
      collection,
      slug
    });
  }
  updateUnpublishedEntryStatus(collection, slug, newStatus) {
    return this.backend.updateUnpublishedEntryStatus(collection, slug, newStatus);
  }
  deleteUnpublishedEntry(collection, slug) {
    return this.backend.deleteUnpublishedEntry(collection, slug);
  }
  publishUnpublishedEntry(collection, slug) {
    return this.backend.publishUnpublishedEntry(collection, slug);
  }
  traverseCursor(cursor, action) {
    return this.backend.traverseCursor(cursor, action);
  }
}
exports.default = GitGateway;