# request.js

    const _ = require('underscore');
    const md5 = require('md5');
    const { extend } = require('underscore');
    const Promise = require('./promise');
    const AV = require('./av');
    const { getSessionToken, ajax } = require('./utils');

    // 计算 X-LC-Sign 的签名方法
    const sign = (key, isMasterKey) => {
      const now = new Date().getTime();
      const signature = md5(now + key);
      if (isMasterKey) {
        return `${signature},${now},master`;
      }
      return `${signature},${now}`;
    };

    const setAppKey = (headers, signKey) => {
      if (signKey) {
        headers['X-LC-Sign'] = sign(AV.applicationKey);
      } else {
        headers['X-LC-Key'] = AV.applicationKey;
      }
    };

    const setHeaders = (authOptions = {}, signKey) => {
      const headers = {
        'X-LC-Id': AV.applicationId,
        'Content-Type': 'application/json;charset=UTF-8',
      };
      let useMasterKey = false;
      if (typeof authOptions.useMasterKey === 'boolean') {
        useMasterKey = authOptions.useMasterKey;
      } else if (typeof AV._config.useMasterKey === 'boolean') {
        useMasterKey = AV._config.useMasterKey;
      }
      if (useMasterKey) {
        if (AV.masterKey) {
          if (signKey) {
            headers['X-LC-Sign'] = sign(AV.masterKey, true);
          } else {
            headers['X-LC-Key'] = `${AV.masterKey},master`;
          }
        } else {
          console.warn('masterKey is not set, fall back to use appKey');
          setAppKey(headers, signKey);
        }
      } else {
        setAppKey(headers, signKey);
      }
      if (AV.hookKey) {
        headers['X-LC-Hook-Key'] = AV.hookKey;
      }
      if (AV._config.production !== null) {
        headers['X-LC-Prod'] = String(AV._config.production);
      }
      headers[!process.env.CLIENT_PLATFORM ? 'User-Agent' : 'X-LC-UA'] =
        AV._sharedConfig.userAgent;

      return Promise.resolve().then(() => {
        // Pass the session token
        const sessionToken = getSessionToken(authOptions);
        if (sessionToken) {
          headers['X-LC-Session'] = sessionToken;
        } else if (!AV._config.disableCurrentUser) {
          return AV.User.currentAsync().then(currentUser => {
            if (currentUser && currentUser._sessionToken) {
              headers['X-LC-Session'] = currentUser._sessionToken;
            }
            return headers;
          });
        }
        return headers;
      });
    };

    const createApiUrl = ({
      service = 'api',
      version = '1.1',
      path,
      // query, // don't care
      // method, // don't care
      // data, // don't care
    }) => {
      let apiURL = AV._config.serverURLs[service];

      if (!apiURL) throw new Error(`undefined server URL for ${service}`);

      if (apiURL.charAt(apiURL.length - 1) !== '/') {
        apiURL += '/';
      }
      apiURL += version;
      if (path) {
        apiURL += path;
      }

      return apiURL;
    };

    /**
     * Low level REST API client. Call REST endpoints with authorization headers.
     * @function AV.request
     * @since 3.0.0
     * @param {Object} options
     * @param {String} options.method HTTP method
     * @param {String} options.path endpoint path, e.g. `/classes/Test/55759577e4b029ae6015ac20`
     * @param {Object} [options.query] query string dict
     * @param {Object} [options.data] HTTP body
     * @param {AuthOptions} [options.authOptions]
     * @param {String} [options.service = 'api']
     * @param {String} [options.version = '1.1']
     */
    const request = ({
      service,
      version,
      method,
      path,
      query,
      data,
      authOptions,
      signKey = true,
    }) => {
      if (!(AV.applicationId && (AV.applicationKey || AV.masterKey))) {
        throw new Error('Not initialized');
      }
      AV._appRouter.refresh();
      const { requestTimeout: timeout } = AV._config;
      const url = createApiUrl({ service, path, version });
      return setHeaders(authOptions, signKey).then(headers =>
        ajax({ method, url, query, data, headers, timeout }).catch(error => {
          let errorJSON = {
            code: error.code || -1,
            error: error.message || error.responseText,
          };
          if (error.response && error.response.code) {
            errorJSON = error.response;
          } else if (error.responseText) {
            try {
              errorJSON = JSON.parse(error.responseText);
            } catch (e) {
              // If we fail to parse the error text, that's okay.
            }
          }
          errorJSON.rawMessage = errorJSON.rawMessage || errorJSON.error;
          if (!AV._sharedConfig.keepErrorRawMessage) {
            errorJSON.error += ` [${error.statusCode || 'N/A'} ${method} ${url}]`;
          }
          // Transform the error into an instance of AVError by trying to parse
          // the error string as JSON.
          const err = new Error(errorJSON.error);
          delete errorJSON.error;
          throw _.extend(err, errorJSON);
        })
      );
    };

    // lagecy request
    const _request = (
      route,
      className,
      objectId,
      method,
      data,
      authOptions,
      query
    ) => {
      let path = '';
      if (route) path += `/${route}`;
      if (className) path += `/${className}`;
      if (objectId) path += `/${objectId}`;
      // for migeration
      if (data && data._fetchWhenSave)
        throw new Error('_fetchWhenSave should be in the query');
      if (data && data._where) throw new Error('_where should be in the query');
      if (method && method.toLowerCase() === 'get') {
        query = extend({}, query, data);
        data = null;
      }
      return request({
        method,
        path,
        query,
        data,
        authOptions,
      });
    };

    AV.request = request;

    module.exports = {
      _request,
      request,
    };