let Fs = require('fs');
let Path = require('path');
let JavascriptObfuscator = require('javascript-obfuscator');

let fmbuild = require('./fmbuild');

let defaultConfig = {
  auto: false,
  files: ['/src/project.js'],
  useAbsPath: false,
  preset: 'lower',
  options: {}
};

let presetFileUrl = 'packages://ccc-obfuscated-code/preset.json';
let presets = null;


let list = ['DemoNewWeixin', 'FMViewBase', 'FMViewWudianMoveBlinkBase', 'FMButton', 'FMItemLayout', 'FMScrollViewLoop', 'FMTouchMaskView', 'AppConfig', 'AppSwitchConfig', 'StoreConfig', 'EventEnum', 'EventMgr', 'FMInterface', 'Main', 'BundleMgr', 'GameMgr', 'RYPlatformMgr', 'RemoteMgr', 'SoundMgr', 'StorageMgr', 'TimerUtils', 'VibrateMgr', 'WXADMgr', 'WudianMgr', 'AesTools', 'HttpUnit', 'NetConfig', 'aes', 'ALD', 'MaiLiang', 'OPPOAPI', 'CachedQQBannerAd', 'QQMiniGameAPI', 'ShareAd', 'TTAPI', 'VIVOAPI', 'CachedWXBannerAd', 'WXAPI', 'RYAD', 'RYSDK', 'RYSTAT', 'User', 'AppPlatform', 'Common', 'DateUtils', 'LogUtils', 'Utilit', 'WXBannerView', 'CrazyClickView', 'Export1View', 'Export2View', 'Export3View', 'ADGameBanner', 'ADListView', 'ADSingle', 'ADWXBanner', 'KRQ_Base', 'LoadingView', 'MiniGameItemPrefab', 'MiniGameView', 'OPPONativeAdViewTemplate', 'ButWudianPrefab', 'QQCrazyClickView', 'QQCrazyClickView2', 'QQGameSettleViewTemplate', 'QQGameViewTemplate', 'QQMainViewTemplate', 'TTExport2Template', 'TTGameSettleViewTemplate', 'TTGameViewTemplate', 'TTMainViewTemplate', 'TTMoreReward', 'TTRelive', 'TTReward', 'TTRewardBox', 'TTShareRecord', 'TTSignIn', 'TTStartTry', 'VVNativeAd1View', 'VVNativeAd2View', 'SettleLayoutBase', 'WCGameSettleViewTemplate', 'WCGameViewTemplate', 'WCMainViewTemplate', 'FMAdItemLayout', 'FMAdViewBase', 'FMAdViewFixed', 'FMAdViewLoop', 'FMViewGameSettleWx', 'FMViewGameWx', 'FMViewHaowanWx', 'FMViewHaowanWx2', 'FMViewLoopExportBig', 'FMViewMainWx', 'FMViewReviveWx', 'FMViewSkinWx', 'FMGameFailScene', 'FMGameScene', 'FMGameWinScene', 'FMMainScene']
let listRandom = [];
let rProIndex = 0;
let tabProRand = {}
let identifiersPrefixName = ""


let csrywTab = {}


/**
 * 保存加工的配置
 * @param {*} config 
 */
function saveConfigProcess(config) {
  let projectPath = Editor.Project.path || Editor.projectPath;
  Editor.log(projectPath)
  let configDirPath = Path.join(projectPath, '/packages/ccc-obfuscated-code/local/');
  Editor.log(configDirPath)
  if (!Fs.existsSync(configDirPath)) Fs.mkdirSync(configDirPath);
  let configFilePath = Path.join(configDirPath, 'configProcess.json');
  let object = {};
  // // 读取本地配置
  // if (Fs.existsSync(configFilePath)) {
  //   object = JSON.parse(Fs.readFileSync(configFilePath, 'utf8'));
  // }
  // // 写入配置
  // for (let key in config) {
  //   object[key] = config[key];
  // }

  let classTab = {}
  for (let index = 0; index < list.length; index++) {
    const element = list[index];
    const element2 = listRandom[index];
    classTab[element] = element2
  }
  object.time = (new Date()).toLocaleString();
  object.identifiersPrefixName = identifiersPrefixName;
  object.class = classTab
  //object.csryw = tabProRand
  object.csryw = csrywTab


  let string = JSON.stringify(object, null, 2);
  Fs.writeFileSync(configFilePath, string);

  Editor.log('[CC] 混淆数据保存', configFilePath);
}



/**
 * 保存配置
 * @param {*} config 
 */
function saveConfig(config) {
  let projectPath = Editor.Project.path || Editor.projectPath;
  Editor.log(projectPath)
  let configDirPath = Path.join(projectPath, '/packages/ccc-obfuscated-code/local/');
  Editor.log(configDirPath)
  if (!Fs.existsSync(configDirPath)) Fs.mkdirSync(configDirPath);
  let configFilePath = Path.join(configDirPath, 'ccc-obfuscated-code.json');
  let object = {};
  // 读取本地配置
  if (Fs.existsSync(configFilePath)) {
    object = JSON.parse(Fs.readFileSync(configFilePath, 'utf8'));
  }
  // 写入配置
  for (let key in config) {
    object[key] = config[key];
  }
  let string = JSON.stringify(object, null, 2);
  Fs.writeFileSync(configFilePath, string);
  Editor.log('[CC] 配置文件路径', configFilePath);
}

/**
 * 读取配置
 */
function getConfig() {
  let projectPath = Editor.Project.path || Editor.projectPath;
  let configFilePath = Path.join(projectPath, '/packages/ccc-obfuscated-code/local/ccc-obfuscated-code.json');
  let config = null;
  if (Fs.existsSync(configFilePath)) {
    config = JSON.parse(Fs.readFileSync(configFilePath, 'utf8'));
    // 删除旧版本(1.0.0)的配置文件
    let projectName = Editor.Project.name || projectPath.slice(projectPath.lastIndexOf('\\') + 1);
    if (config[projectName]) {
      Fs.unlinkSync(configFilePath);
      config = null;
    }
  }
  if (!config) {
    config = defaultConfig;
    config.options = getPreset('off');
    if (config.preset !== 'off') {
      let preset = getPreset(config.preset);
      for (let key in preset) {
        config.options[key] = preset[key];
      }
    }
  }
  return config;
};

/**
 * 读取预设参数
 */
function getPreset(type) {
  if (presets) {
    return presets[type];
  } else {
    let presetFilePath = Editor.url(presetFileUrl);
    if (Fs.existsSync(presetFilePath)) {
      presets = JSON.parse(Fs.readFileSync(presetFilePath, 'utf8'));
      return presets[type];
    } else {
      return null;
    }
  }
};

/**
 * 混淆
 * @param {*} path 文件路径
 * @param {*} options 混淆参数
 */
function obfuscate(path, options) {
  let sourceCode = Fs.readFileSync(path, 'utf8');
  let obfuscationResult = JavascriptObfuscator.obfuscate(sourceCode, options);
  let obfuscatedCode = obfuscationResult.getObfuscatedCode();
  Fs.writeFileSync(path, obfuscatedCode);
}

//读取js 文件
function readFileList(dir, filesList = []) {
  const files = Fs.readdirSync(dir);
  files.forEach((item, index) => {
    var fullPath = Path.join(dir, item);
    const stat = Fs.statSync(fullPath);
    if (stat.isDirectory()) {
      readFileList(Path.join(dir, item), filesList);  //递归读取文件
    } else {
      if (Path.extname(fullPath) === '.js') {
        filesList.push(fullPath);
      }
    }
  });
  return filesList;
}

//随机串
function randomString() {
  let num = Math.floor(Math.random() * 5);
  num = num + 7;
  var t = "_ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz",
    a = t.length,
    n = "";
  for (i = 0; i < num; i++)
    n += t.charAt(Math.floor(Math.random() * a));
  return n
}

//随机串 class name
function randomStringClassName(num) {
  var t = "_ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz",
    a = t.length,
    n = "";
  for (i = 0; i < num; i++)
    n += t.charAt(Math.floor(Math.random() * a));
  return n
}

//随机串 IdentifiersPrefix
function randomStringIdentifiersPrefix(num) {
  var t = "_ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz",
    a = t.length,
    n = "";
  for (i = 0; i < num; i++)
    n += t.charAt(Math.floor(Math.random() * a));
  return n
}



//替换
function replace_csryw(path) {
  let projectPath = Editor.Project.path || Editor.projectPath;
  let configFilePath = Path.join(projectPath, path);
  var filesList = [];
  readFileList(configFilePath, filesList);

  for (let index = 0; index < filesList.length; index++) {
    const element = filesList[index];
    let data = Fs.readFileSync(element);

    if (data) {
      data = data.toString() + "";
      for (var key in tabProRand) {
        data = data.replace(new RegExp("." + key, "gm"), tabProRand[key]);
      }

      Fs.writeFileSync(element, data);
    }
  }
}


//混淆 assets subpackages
function replaceFile(actualPlatform) {
  if (actualPlatform == "wechatgame") {

    //查找关键字
    Editor.log("开始查找")
    tabProRand = {};
    rProIndex = Math.floor(Math.random() * 20);
    find_csryw_file("/build/wechatgame/assets/", tabProRand);
    find_csryw_file("/build/wechatgame/subpackages/", tabProRand);
    Editor.log(tabProRand);
    Editor.log("总共找到关键字")


    // //替换的字符
    // let cstr = randomString()
    // Editor.warn("替换 _csryw 为 " + cstr);
    replace_csryw("/build/wechatgame/assets/")
    replace_csryw("/build/wechatgame/subpackages/")
  } else {
    Editor.warn("当前平台没有处理 关键字 替换" + actualPlatform);
  }
}



//替换
function replace_className(path) {
  let projectPath = Editor.Project.path || Editor.projectPath;
  let configFilePath = Path.join(projectPath, path);
  var filesList = [];
  readFileList(configFilePath, filesList);

  for (let index = 0; index < filesList.length; index++) {
    const element = filesList[index];
    let data = Fs.readFileSync(element);
    if (data) {
      data = data.toString() + "";
      for (var i = 0; i < list.length; i++) {
        data = data.replace(new RegExp(",\'" + list[i] + "\'", "gm"), ",\'" + listRandom[i] + "\'");
        data = data.replace(new RegExp(":\'" + list[i] + "\'", "gm"), ":\'" + listRandom[i] + "\'");
        data = data.replace(new RegExp("\'" + list[i] + "\':", "gm"), "\'" + listRandom[i] + "\':");
        data = data.replace("\[\'" + list[i] + "\'", "\[\'" + listRandom[i] + "\'");
        //data = data.replace(new RegExp("/" + list[i] + "", "gm"), "/" + listRandom[i] + "");
      }
      Fs.writeFileSync(element, data);
    }
  }
}

//文件中查找
function find_csryw_file(path, array) {
  let projectPath = Editor.Project.path || Editor.projectPath;
  let configFilePath = Path.join(projectPath, path);
  var filesList = [];
  readFileList(configFilePath, filesList);
  Editor.log(filesList)
  for (let index = 0; index < filesList.length; index++) {
    const element = filesList[index];

    let data = Fs.readFileSync(element);
    if (data) {
      data = data.toString() + "";
      let cnextid = data.indexOf('_csryw'); // 字符出现的位置
      while (cnextid !== -1) {
        let cid1 = data.lastIndexOf(".", cnextid);
        let cid2 = data.lastIndexOf("\"", cnextid);
        let cid3 = data.lastIndexOf("{", cnextid);
        let cid4 = data.lastIndexOf(",", cnextid);
        let cid = -1;
        cid = cid1 > cid ? cid1 : cid;
        cid = cid2 > cid ? cid2 : cid;
        cid = cid3 > cid ? cid3 : cid;
        cid = cid4 > cid ? cid4 : cid;


        if (cid != -1) {
          let str = data.slice(cid + 1, cnextid + 6);
          if (!array[str]) {

            let strRandName = randomStringClassName(4);
            let nstr = strRandName.slice(0, 2) + rProIndex + strRandName.slice(2);
            rProIndex = rProIndex + Math.floor(Math.random() * 10);
            array[str] = nstr;
          }
        }
        cnextid = data.indexOf('_csryw', cnextid + 1); // 从字符串出现的位置的下一位置开始继续查找
      }
    }

  }
}

//查找 _csryw
function find_csryw() {
}



function test(actualPlatform) {

  if (actualPlatform == "wechatgame") {

    listRandom = [];
    let cindex = Math.floor(Math.random() * 100);
    for (var i = 0; i < list.length; i++) {
      let str = randomStringClassName(2);
      let nstr = str.slice(0, 1) + cindex + str.slice(1);
      cindex = cindex + Math.floor(Math.random() * 40);
      listRandom.push(nstr);
    }
    //Editor.log(listRandom)
    Editor.warn("替换 class name ");
    replace_className("/build/wechatgame/assets/")
    replace_className("/build/wechatgame/subpackages/")
  } else {
    Editor.warn("当前平台没有处理 class 关键字 替换" + actualPlatform);
  }
}


//第一次混淆
function javaObfuscator(filePath) {
  let sourceCode = Fs.readFileSync(filePath, 'utf8');
  let obfuscationResult = JavascriptObfuscator.obfuscate(sourceCode, {
    "compact": true,
    "controlFlowFlattening": false,
    "controlFlowFlatteningThreshold": 0.75,
    "deadCodeInjection": false,
    "deadCodeInjectionThreshold": 0.4,
    "debugProtection": false,
    "debugProtectionInterval": false,
    "disableConsoleOutput": false,
    "domainLock": [],
    "identifierNamesGenerator": "mangled",
    "identifiersDictionary": [],
    "identifiersPrefix": "",
    "inputFileName": "",
    "log": false,
    "renameGlobals": false,
    "reservedNames": [],
    "reservedStrings": [],
    "rotateStringArray": true,
    "seed": "",
    "selfDefending": false,
    "shuffleStringArray": false,
    "sourceMap": false,
    "sourceMapBaseUrl": "",
    "sourceMapFileName": "",
    "sourceMapMode": "separate",
    "splitStrings": false,
    "splitStringsChunkLength": 10,
    "stringArray": false,
    "stringArrayEncoding": false,
    "stringArrayThreshold": 0.75,
    "target": "browser",
    "transformObjectKeys": false,
    "unicodeEscapeSequence": false
  });
  let obfuscatedCode = obfuscationResult.getObfuscatedCode();
  Fs.writeFileSync(filePath, obfuscatedCode);
}


//文件中查找
function find_csryw_file2(path) {
  let data = Fs.readFileSync(path);
  if (data) {
    data = data.toString() + "";
    let cnextid = data.indexOf('_csryw'); // 字符出现的位置
    while (cnextid !== -1) {
      let cid1 = data.lastIndexOf(".", cnextid);
      let cid2 = data.lastIndexOf("\"", cnextid);
      let cid3 = data.lastIndexOf("{", cnextid);
      let cid4 = data.lastIndexOf(",", cnextid);
      let cid5 = data.lastIndexOf(" ", cnextid);
      let cid6 = data.lastIndexOf("(", cnextid);
      let cid7 = data.lastIndexOf("[", cnextid);
      let cid = -1;
      cid = cid1 > cid ? cid1 : cid;
      cid = cid2 > cid ? cid2 : cid;
      cid = cid3 > cid ? cid3 : cid;
      cid = cid4 > cid ? cid4 : cid;
      cid = cid5 > cid ? cid5 : cid;
      cid = cid6 > cid ? cid6 : cid;
      cid = cid7 > cid ? cid7 : cid;

      if (cid != -1) {
        let str = data.slice(cid + 1, cnextid + 6);
        if (!csrywTab[str]) {

          let strRandName = randomStringClassName(4);
          let nstr = strRandName.slice(0, 2) + rProIndex + strRandName.slice(2);
          rProIndex = rProIndex + Math.floor(Math.random() * 10);
          csrywTab[str] = nstr;
        }
      }
      cnextid = data.indexOf('_csryw', cnextid + 1); // 从字符串出现的位置的下一位置开始继续查找
    }
  }
}




function rmdir(filePath) {
  if (Fs.existsSync(filePath)) {
    if (Fs.statSync(filePath).isDirectory()) {
      let files = Fs.readdirSync(filePath);
      files.forEach((file, idx) => {
        var curPath = filePath + Path.sep + file;
        if (Fs.statSync(curPath).isDirectory()) {
          rmdir(curPath);
        } else {
          Fs.unlinkSync(curPath);
        }
      });
      Fs.rmdirSync(filePath);
    } else {
      Fs.unlinkSync(filePath);
    }
  }
}

function copyFolder2(srcDir, tarDir) {
  // Editor.log(srcDir + "   " + tarDir)
  if (!Fs.existsSync(tarDir)) Fs.mkdirSync(tarDir);
  let files = Fs.readdirSync(srcDir);
  for (let i = 0; i < files.length; i++) {
    var srcPath = Path.join(srcDir, files[i]);
    var tarPath = Path.join(tarDir, files[i]);
    let result = Fs.statSync(srcPath);
    if (result.isFile()) {
      Fs.copyFileSync(srcPath, tarPath);
    } else if (result.isDirectory()) {
      copyFolder(srcPath, tarPath, null);
    }
  }
}

//! 将srcDir文件下的文件、文件夹递归的复制到tarDir下
function copyFolder(srcDir, tarDir, cb) {
  // Editor.log(srcDir + "   " + tarDir)
  if (!Fs.existsSync(tarDir)) Fs.mkdirSync(tarDir);
  let files = Fs.readdirSync(srcDir);
  for (let i = 0; i < files.length; i++) {
    var srcPath = Path.join(srcDir, files[i]);
    var tarPath = Path.join(tarDir, files[i]);
    let result = Fs.statSync(srcPath);

    if (result.isFile()) {
      let name = Path.extname(srcPath);
      if (name == ".js") {//|| name == ".json"
        Fs.copyFileSync(srcPath, tarPath);
      }
      if (name == ".js") {
        //Editor.log("处理 " + srcPath)
        find_csryw_file2(srcPath);
      }
    } else if (result.isDirectory()) {
      copyFolder(srcPath, tarPath, null);
    }
  }
  if (cb) {
    cb();
  }
}



//替换 文件关键字
function replace_csryw_write(srcPath) {
  let name = Path.extname(srcPath);
  if (name == ".js" || name == ".json") {
    let data = Fs.readFileSync(srcPath);
    if (data) {
      data = data.toString() + "";
      for (var key in csrywTab) {
        data = data.replace(new RegExp(key, "gm"), csrywTab[key]);
      }
      Fs.writeFileSync(srcPath, data);
    }
  }
}


//替换关键字
function replace_csryw2(srcDir) {
  let result = Fs.statSync(srcDir);
  if (result.isFile()) {
    replace_csryw_write(srcDir);
  } else if (result.isDirectory()) {
    let files = Fs.readdirSync(srcDir);
    for (let i = 0; i < files.length; i++) {
      var srcPath = Path.join(srcDir, files[i]);
      let result = Fs.statSync(srcPath);
      if (result.isFile()) {
        replace_csryw_write(srcPath);
      } else if (result.isDirectory()) {
        replace_csryw2(srcPath);
      }
    }
  }
}

// function findCsryw2(srcDir, cb) {
//   let files = Fs.readdirSync(srcDir);
//   for (let i = 0; i < files.length; i++) {
//     var srcPath = Path.join(srcDir, files[i]);
//     let result = Fs.statSync(srcPath);
//     if (result.isFile()) {
//       let name = Path.extname(srcPath);
//       if (name == ".js") {
//         find_csryw_file2(srcPath);
//       }
//     } else if (result.isDirectory()) {
//       findCsryw2(srcPath, null);
//     }
//   }
//   if (cb) {
//     cb();
//   }
// }


//还原
function test3() {
  let projectPath = Editor.Project.path || Editor.projectPath;
  let configFilePath = Path.join(projectPath, "/library/");
  let srcPath = Path.join(configFilePath, "/imports/");
  let decPath = Path.join(configFilePath, "/importsOther/");
  copyFolder2(decPath, srcPath)
  Editor.log("还原完成。。。")
}


function test2() {
  let projectPath = Editor.Project.path || Editor.projectPath;
  let configFilePath = Path.join(projectPath, "/library/");
  let srcPath = Path.join(configFilePath, "/imports/");
  let decPath = Path.join(configFilePath, "/importsOther/");

  Editor.log("文件准备处理中。。。。")
  //重置
  csrywTab = {}

  rmdir(decPath);
  Editor.log("清理" + decPath)
  //创建文件夹
  if (!Fs.existsSync(decPath)) {
    Fs.mkdirSync(decPath);
    Editor.log("创建 " + decPath)
  }
  //拷贝过去 并 找到关键字 随机
  copyFolder(srcPath, decPath)

  Editor.log("拷贝查找关键字")
  // csrywTab["AUTO_VIEW_csryw"] = "123"
  // csrywTab["ExportViewType_csryw"] = "1244443"

  //let str = Path.join(decPath, "/14/147d4940-1ed7-44fb-bbfc-1fb26eb8d73a.js");
  replace_csryw2(srcPath)

  Editor.log("文件处理结束啦")

}

module.exports = {

  load() {
    Editor.Builder.on('build-start', this.onBuildStart);
    // Editor.Builder.on('build-finished', this.onBuildFinished);
    //Editor.Builder.on('before-change-files', this.onBeforeChangeFiles);
    Editor.Builder.on('build-finished', this.onBeforeChangeFiles);
  },

  unload() {
    Editor.Builder.removeListener('build-start', this.onBuildStart);
    // Editor.Builder.removeListener('build-finished', this.onBuildFinished);
    // Editor.Builder.removeListener('before-change-files', this.onBeforeChangeFiles);
    Editor.Builder.removeListener('build-finished', this.onBeforeChangeFiles);
  },

  messages: {

    'open-panel'() {
      Editor.log('[CC] 代码混淆工具/构建后自动混淆');
      Editor.Panel.open('ccc-obfuscated-code');
    },

    // TODO
    // 'open-panel'() {
    //   Editor.log('[CC] 代码混淆工具/主动混淆');
    //   Editor.Panel.open('ccc-obfuscated-code-do');
    // },

    'save-config'(event, config) {
      Editor.log('[CC] 保存配置');
      saveConfig(config);
      event.reply(null, true);

      //new fmbuild();

    },

    'read-config'(event) {
      Editor.log('[CC] 读取配置');
      let config = getConfig();
      event.reply(null, config);
    },

    'get-preset'(event, name) {
      Editor.log('[CC] 读取预设', name);
      let preset = getPreset(name);
      if (preset) {
        event.reply(null, preset);
      } else {
        Editor.log('[CC] 预设文件已丢失');
        Editor.log('[CC] 文件下载地址 https://gitee.com/ifaswind/ccc-obfuscated-code/blob/master/preset.json');
        event.reply(null, {});
      }
    }

  },

  onBuildStart(options, callback) {
    let config = getConfig();
    if (config.auto) {
      Editor.log('[CC] 将在项目构建完成后自动混淆代码')
      try {
        test2();
      } catch (err) {
        Editor.error(err)
      }
    }
    callback();
  },


  onBeforeChangeFiles(options, callback) {

    let config = getConfig();
    if (config.auto) {
      //replaceFile(options.actualPlatform);
      Editor.log('[CC] 正在混淆代码');

      // try {
      //   for (let i = 0; i < config.files.length; i++) {
      //     if (config.files[i] === '') continue;
      //     let path = config.useAbsPath ? config.files[i] : Path.join(options.dest, config.files[i]);
      //     if (Fs.existsSync(path)) {
      //       Editor.log('[CC] 混淆文件', path);
      //       //javaObfuscator(path)
      //       obfuscate(path, config.options);
      //     } else {
      //       Editor.warn('[CC] 文件不存在', path);
      //     }
      //   }
      //   test3();
      // } catch (err) {
      //   Editor.error(err)
      // }
      try {
        test3();//还原
        test(options.actualPlatform);//替换class
        identifiersPrefixName = randomStringIdentifiersPrefix(7)
        Editor.log("identifiersPrefixName " + identifiersPrefixName)
        config.options.identifiersPrefix = identifiersPrefixName
        config.options.renameGlobals = true;

        for (let i = 0; i < config.files.length; i++) {
          if (config.files[i] === '') continue;
          let path = config.useAbsPath ? config.files[i] : Path.join(options.dest, config.files[i]);
          if (Fs.existsSync(path)) {
            Editor.log('[CC] 混淆文件', path);
            obfuscate(path, config.options);
          } else {
            Editor.warn('[CC] 文件不存在', path);
          }
        }
      } catch (err) {
        Editor.error(err)
      }

      saveConfigProcess();
      Editor.warn('[CC] 混淆已结束');

    }

    callback();
  },

}