Просмотр исходного кода

Merge branch 'feat/growi-bot' into feat/GW-5798-enable-display-mulutiple-ws-names-and-growi-app-names

Shun Miyazawa 4 лет назад
Родитель
Сommit
5b1e0760e0
26 измененных файлов с 549 добавлено и 440 удалено
  1. 0 1
      packages/slack/package.json
  2. 15 4
      packages/slack/src/middlewares/verify-slack-request.ts
  3. 11 0
      packages/slack/src/utils/logger/index.ts
  4. 14 19
      packages/slack/src/utils/slash-command-parser.test.ts
  5. 7 2
      packages/slack/src/utils/webclient-factory.ts
  6. 8 0
      packages/slackbot-proxy/src/Server.ts
  7. 25 3
      packages/slackbot-proxy/src/controllers/slack.ts
  8. 6 9
      packages/slackbot-proxy/src/services/RegisterService.ts
  9. 12 0
      resource/locales/en_US/admin/admin.json
  10. 12 0
      resource/locales/ja_JP/admin/admin.json
  11. 12 0
      resource/locales/zh_CN/admin/admin.json
  12. 14 4
      src/client/js/components/Admin/SlackIntegration/CustomBotWithProxySettings.jsx
  13. 163 5
      src/client/js/components/Admin/SlackIntegration/CustomBotWithProxySettingsAccordion.jsx
  14. 18 20
      src/client/js/components/Admin/SlackIntegration/CustomBotWithoutProxyIntegrationCard.jsx
  15. 29 3
      src/client/js/components/Admin/SlackIntegration/CustomBotWithoutProxySettings.jsx
  16. 17 17
      src/client/js/components/Admin/SlackIntegration/CustomBotWithoutProxySettingsAccordion.jsx
  17. 65 0
      src/client/js/components/Admin/SlackIntegration/DeleteSlackBotSettingsModal.jsx
  18. 81 6
      src/client/js/components/Admin/SlackIntegration/OfficialbotSettingsAccordion.jsx
  19. 5 28
      src/client/js/components/Admin/SlackIntegration/SlackIntegration.jsx
  20. 0 1
      src/client/styles/scss/_admin.scss
  21. 1 0
      src/server/models/index.js
  22. 10 0
      src/server/models/slack-app-integration.js
  23. 1 1
      src/server/routes/apiv3/index.js
  24. 0 0
      src/server/routes/apiv3/slack-integration-settings.js
  25. 17 4
      src/server/service/slackbot.js
  26. 6 313
      yarn.lock

+ 0 - 1
packages/slack/package.json

@@ -21,7 +21,6 @@
     "universal-bunyan": "^0.9.2"
   },
   "devDependencies": {
-    "@slack/bolt": "^3.3.0",
     "@types/express": "^4.17.11",
     "@types/jest": "^26.0.22",
     "@typescript-eslint/eslint-plugin": "^4.18.0",

+ 15 - 4
packages/slack/src/middlewares/verify-slack-request.ts

@@ -2,8 +2,11 @@ import { createHmac, timingSafeEqual } from 'crypto';
 import { stringify } from 'qs';
 import { Response, NextFunction } from 'express';
 
+import loggerFactory from '../utils/logger';
 import { RequestFromSlack } from '../interfaces/request-from-slack';
 
+const logger = loggerFactory('@growi/slack:middlewares:verify-slack-request');
+
 /**
  * Verify if the request came from slack
  * See: https://api.slack.com/authentication/verifying-requests-from-slack
@@ -12,7 +15,9 @@ export const verifySlackRequest = (req: RequestFromSlack, res: Response, next: N
   const signingSecret = req.slackSigningSecret;
 
   if (signingSecret == null) {
-    return res.status(400).send({ message: 'No signing secret.' });
+    const message = 'No signing secret.';
+    logger.warn(message, { body: req.body });
+    return res.status(400).send({ message });
   }
 
   // take out slackSignature and timestamp from header
@@ -20,13 +25,17 @@ export const verifySlackRequest = (req: RequestFromSlack, res: Response, next: N
   const timestamp = req.headers['x-slack-request-timestamp'];
 
   if (slackSignature == null || timestamp == null) {
-    return res.status(403).send({ message: 'Forbidden. Enter from Slack workspace' });
+    const message = 'Forbidden. Enter from Slack workspace';
+    logger.warn(message, { body: req.body });
+    return res.status(403).send({ message });
   }
 
   // protect against replay attacks
   const time = Math.floor(new Date().getTime() / 1000);
   if (Math.abs(time - timestamp) > 300) {
-    return res.send('Verification failed.');
+    const message = 'Verification failed.';
+    logger.warn(message, { body: req.body });
+    return res.status(403).send({ message });
   }
 
   // generate growi signature
@@ -41,5 +50,7 @@ export const verifySlackRequest = (req: RequestFromSlack, res: Response, next: N
     return next();
   }
 
-  return res.send('Verification failed.');
+  const message = 'Verification failed.';
+  logger.warn(message, { body: req.body });
+  return res.status(403).send({ message });
 };

+ 11 - 0
packages/slack/src/utils/logger/index.ts

@@ -0,0 +1,11 @@
+import Logger from 'bunyan';
+import { createLogger } from 'universal-bunyan';
+
+const loggerFactory = function(name: string): Logger {
+  return createLogger({
+    name,
+    config: { default: 'info' },
+  });
+};
+
+export default loggerFactory;

+ 14 - 19
packages/slack/src/utils/slash-command-parser.test.ts

@@ -1,19 +1,14 @@
-import { SlashCommand } from '@slack/bolt';
 import { InvalidGrowiCommandError } from '../models/errors';
 
 import { parseSlashCommand } from './slash-command-parser';
 
-const SlashCommandMock = jest.fn<SlashCommand, [string]>().mockImplementation((text) => {
-  return { text } as SlashCommand;
-});
-
 describe('parseSlashCommand', () => {
 
   describe('without growiCommandType', () => {
     test('throws InvalidGrowiCommandError', () => {
       // setup
-      const slashCommandText = '';
-      const slashCommand = new SlashCommandMock(slashCommandText);
+      const text = '';
+      const slashCommand = { text };
 
       // when/then
       expect(() => {
@@ -24,56 +19,56 @@ describe('parseSlashCommand', () => {
 
   test('returns a GrowiCommand instance with empty growiCommandArgs', () => {
     // setup
-    const slashCommandText = 'search';
-    const slashCommand = new SlashCommandMock(slashCommandText);
+    const text = 'search';
+    const slashCommand = { text };
 
     // when
     const result = parseSlashCommand(slashCommand);
 
     // then
-    expect(result.text).toBe(slashCommandText);
+    expect(result.text).toBe(text);
     expect(result.growiCommandType).toBe('search');
     expect(result.growiCommandArgs).toStrictEqual([]);
   });
 
   test('returns a GrowiCommand instance with space growiCommandType', () => {
     // setup
-    const slashCommandText = '   search   ';
-    const slashCommand = new SlashCommandMock(slashCommandText);
+    const text = '   search   ';
+    const slashCommand = { text };
 
     // when
     const result = parseSlashCommand(slashCommand);
 
     // then
-    expect(result.text).toBe(slashCommandText);
+    expect(result.text).toBe(text);
     expect(result.growiCommandType).toBe('search');
     expect(result.growiCommandArgs).toStrictEqual([]);
   });
 
   test('returns a GrowiCommand instance with space growiCommandArgs', () => {
     // setup
-    const slashCommandText = '   search hoge   ';
-    const slashCommand = new SlashCommandMock(slashCommandText);
+    const text = '   search hoge   ';
+    const slashCommand = { text };
 
     // when
     const result = parseSlashCommand(slashCommand);
 
     // then
-    expect(result.text).toBe(slashCommandText);
+    expect(result.text).toBe(text);
     expect(result.growiCommandType).toBe('search');
     expect(result.growiCommandArgs).toStrictEqual(['hoge']);
   });
 
   test('returns a GrowiCommand instance', () => {
     // setup
-    const slashCommandText = 'search keyword1 keyword2';
-    const slashCommand = new SlashCommandMock(slashCommandText);
+    const text = 'search keyword1 keyword2';
+    const slashCommand = { text };
 
     // when
     const result = parseSlashCommand(slashCommand);
 
     // then
-    expect(result.text).toBe(slashCommandText);
+    expect(result.text).toBe(text);
     expect(result.growiCommandType).toBe('search');
     expect(result.growiCommandArgs).toStrictEqual(['keyword1', 'keyword2']);
   });

+ 7 - 2
packages/slack/src/utils/webclient-factory.ts

@@ -2,6 +2,11 @@ import { LogLevel, WebClient } from '@slack/web-api';
 
 const isProduction = process.env.NODE_ENV === 'production';
 
-export const generateWebClient = (botToken: string): WebClient => {
-  return new WebClient(botToken, { logLevel: isProduction ? LogLevel.DEBUG : LogLevel.INFO });
+/**
+ * Generate WebClilent instance
+ * @param token Slack Bot Token or Proxy Server URI
+ * @returns
+ */
+export const generateWebClient = (token: string, serverUri?: string): WebClient => {
+  return new WebClient(token, { slackApiUrl: serverUri, logLevel: isProduction ? LogLevel.DEBUG : LogLevel.INFO });
 };

+ 8 - 0
packages/slackbot-proxy/src/Server.ts

@@ -75,6 +75,14 @@ export class Server {
   @Inject()
   injector: InjectorService;
 
+  $onInit(): Promise<any> | void {
+    const serverUri = process.env.SERVER_URI;
+
+    if (serverUri === undefined) {
+      throw new Error('The environment variable \'SERVER_URI\' must be defined.');
+    }
+  }
+
   $beforeRoutesInit(): void {
     this.app
       .use(cookieParser())

+ 25 - 3
packages/slackbot-proxy/src/controllers/slack.ts

@@ -130,7 +130,7 @@ export class SlackCtrl {
 
   @Post('/interactions')
   @UseBefore(AuthorizeInteractionMiddleware)
-  async handleInteraction(@Req() req: AuthedReq, @Res() res: Res): Promise<void|string> {
+  async handleInteraction(@Req() req: AuthedReq, @Res() res: Res): Promise<void|string|Res|WebAPICallResult> {
     logger.info('receive interaction', req.body);
     logger.info('receive interaction', req.authorizeResult);
 
@@ -153,17 +153,39 @@ export class SlackCtrl {
     const { type } = payload;
 
     // register
+    // response_urls is an array but the element included is only one.
     if (type === 'view_submission' && payload.response_urls[0].action_id === 'submit_growi_url_and_access_tokens') {
       await this.registerService.upsertOrderRecord(this.orderRepository, installation, payload);
-      await this.registerService.showProxyUrl(authorizeResult, payload);
+      await this.registerService.notifyServerUriToSlack(authorizeResult, payload);
       return;
     }
 
     /*
      * forward to GROWI server
      */
-    // TODO: forward to GROWI server by GW-5866
+    const relations = await this.relationRepository.find({ installation: installation?.id });
 
+    const promises = relations.map((relation: Relation) => {
+      // generate API URL
+      const url = new URL('/_api/v3/slack-integration/proxied/interactions', relation.growiUri);
+      return axios.post(url.toString(), {
+        ...body,
+        tokenPtoG: relation.tokenPtoG,
+      });
+    });
+
+    // pickup PromiseRejectedResult only
+    const results = await Promise.allSettled(promises);
+    const rejectedResults: PromiseRejectedResult[] = results.filter((result): result is PromiseRejectedResult => result.status === 'rejected');
+    const botToken = installation?.data.bot?.token;
+
+    try {
+      // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+      return postEphemeralErrors(rejectedResults, body.channel_id, body.user_id, botToken!);
+    }
+    catch (err) {
+      logger.error(err);
+    }
   }
 
   @Post('/events')

+ 6 - 9
packages/slackbot-proxy/src/services/RegisterService.ts

@@ -6,7 +6,6 @@ import { GrowiCommandProcessor } from '~/interfaces/growi-command-processor';
 import { OrderRepository } from '~/repositories/order';
 import { Installation } from '~/entities/installation';
 
-
 const isProduction = process.env.NODE_ENV === 'production';
 
 @Service()
@@ -37,6 +36,8 @@ export class RegisterService implements GrowiCommandProcessor {
           generateInputSectionBlock('growiDomain', 'GROWI domain', 'contents_input', false, 'https://example.com'),
           generateInputSectionBlock('growiAccessToken', 'GROWI ACCESS_TOKEN', 'contents_input', false, 'jBMZvpk.....'),
           generateInputSectionBlock('proxyToken', 'PROXY ACCESS_TOKEN', 'contents_input', false, 'jBMZvpk.....'),
+          // added an input block to make response_url enabled and get info (block_id, action_id, channel_id, response_url)
+          // refer to https://api.slack.com/surfaces/modals/using#modal_response_url
           {
             block_id: 'channel_to_post_proxy_url',
             type: 'input',
@@ -79,19 +80,15 @@ export class RegisterService implements GrowiCommandProcessor {
     }
   }
 
-  async showProxyUrl(
+  async notifyServerUriToSlack(
       // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
       authorizeResult:AuthorizeResult, payload: any,
   ): Promise<void> {
 
-    // TODO: implement for when proxy URL is undefined by GW-5834
-    let proxyURL;
-    if (process.env.PROXY_URL != null) {
-      proxyURL = process.env.PROXY_URL;
-    }
-
     const { botToken } = authorizeResult;
 
+    const serverUri = process.env.SERVER_URI;
+
     const client = new WebClient(botToken, { logLevel: isProduction ? LogLevel.DEBUG : LogLevel.INFO });
 
     await client.chat.postEphemeral({
@@ -102,7 +99,7 @@ export class RegisterService implements GrowiCommandProcessor {
       text: 'Proxy URL',
       blocks: [
         generateMarkdownSectionBlock('Please enter and update the following Proxy URL to slack bot setting form in your GROWI'),
-        generateMarkdownSectionBlock(`Proxy URL: ${proxyURL}`),
+        generateMarkdownSectionBlock(`Proxy URL: ${serverUri}`),
       ],
     });
     return;

+ 12 - 0
resource/locales/en_US/admin/admin.json

@@ -288,14 +288,26 @@
       "discard": "Discard",
       "generate": "Generate"
     },
+    "delete": "Delete",
+    "cooperation_procedure": "Cooperation procedure",
     "official_bot_settings": "Official bot Settings",
     "custom_bot_without_proxy_settings": "Custom Bot without proxy Settings",
+    "reset": "Reset",
+    "delete_slackbot_settings": "Reset Slack Bot settings",
+    "slackbot_settings_notice": "Reset",
     "accordion": {
       "create_bot": "Create Bot",
       "how_to_create_a_bot": "How to create a bot",
       "how_to_install": "How to install",
       "install_bot_to_slack": "Install Bot To Slack",
       "install_now": "Install now",
+      "generate_access_token": "Generate Access Token",
+      "register_for_growi_official_bot_proxy_service": "Register for GROWI Official Bot Proxy Service",
+      "enter_growi_register_on_slack": "Enter `/growi register` on slack",
+      "paste_growi_url": "Enter `http://localhost:3000` for <b>GROWI URL</b>",
+      "enter_access_token_for_growi_and_proxy": "Enter <b>Access Token for GROWI</b> and <b>Access Token for Proxy</b>",
+      "set_proxy_url_on_growi": "Set Proxy URL on GROWI",
+      "enter_proxy_url_and_update": "Enter <b>Proxy URL</b> shown on Slack and click Update",
       "select_install_your_app": "Select \"Install your app\".",
       "select_install_to_workspace": "Select \"Install to Workspace\".",
       "register_official_bot_proxy_service": "Issue Access Token / Register GROWI Official Bot Proxy Service",

+ 12 - 0
resource/locales/ja_JP/admin/admin.json

@@ -286,13 +286,25 @@
       "discard": "破棄",
       "generate": "発行"
     },
+    "delete": "削除",
+    "cooperation_procedure": "連携手順",
     "custom_bot_without_proxy_settings": "Custom Bot (Without-Proxy) 設定",
+    "reset":"リセット",
+    "delete_slackbot_settings": "Slack Bot 設定をリセットする",
+    "slackbot_settings_notice": "リセットします",
     "accordion": {
       "create_bot": "Bot を作成する",
       "how_to_create_a_bot": "作成方法はこちら",
       "how_to_install": "インストール方法はこちら",
       "install_bot_to_slack": "Bot を Slack にインストールする",
       "install_now": "今すぐインストール",
+      "generate_access_token": "Access Tokenの発行",
+      "register_for_growi_official_bot_proxy_service": "GROWI Official Bot Proxy サービスへの登録",
+      "enter_growi_register_on_slack": "Slack上で `/growi register` と打つ",
+      "paste_growi_url": "<b>GROWI URL</b>には`http://localhost:3000`を貼り付ける",
+      "enter_access_token_for_growi_and_proxy": "上記で発行した<b>Access Token for GROWI</b> と <b>Access Token for Proxy</b>を入れる",
+      "set_proxy_url_on_growi": "ProxyのURLをGROWIに登録する",
+      "enter_proxy_url_and_update": "Slack上に通知された<b>Proxy URL</b>を入力し、更新してください。",
       "select_install_your_app": "Install your app をクリックします。",
       "select_install_to_workspace": "Install to Workspace をクリックします。",
       "register_official_bot_proxy_service": "アクセストークンの発行 / GROWI Official Bot Proxy サービスへの登録",

+ 12 - 0
resource/locales/zh_CN/admin/admin.json

@@ -296,13 +296,25 @@
       "discard": "丢弃",
       "generate": "生成"
     },
+    "delete": "取消",
+    "cooperation_procedure": "协作程序",
     "custom_bot_without_proxy_settings": "Custom Bot (Without-Proxy) 设置",
+    "reset":"重置",
+    "delete_slackbot_settings": "重置 Slack Bot 设置",
+    "slackbot_settings_notice": "重置",
     "accordion": {
       "create_bot": "创建 Bot",
       "how_to_create_a_bot": "如何创建一个 Bot",
       "how_to_install": "点击这里查看安装说明",
       "install_bot_to_slack": "将 Bot 安装到 Slack",
       "install_now": "现在安装",
+      "generate_access_token": "生成Access Token",
+      "register_for_growi_official_bot_proxy_service": "注册 GROWI Official Bot Proxy Service",
+      "enter_growi_register_on_slack": "在Slack中,输入`/growi register`。",
+      "paste_growi_url": "将`http://localhost:3000`粘贴到 <b>GROWI URL</b> 网址中",
+      "enter_access_token_for_growi_and_proxy": "插入上面发出的 <b>Access Token for GROWI</b> 和 <b>Access Token for Proxy</b>。",
+      "set_proxy_url_on_growi": "向GROWI注册Proxy的URL",
+      "enter_proxy_url_and_update": "按照Slack上的通知输入并更新你的 <b>Proxy URL</b>。",
       "select_install_your_app": "选择 \"Install your app\"。",
       "select_install_to_workspace": "选择 \"Install to Workspace\"。",
       "register_official_bot_proxy_service": "发行访问令牌 / 注册 GROWI 官方 Bot 代理服务",

+ 14 - 4
src/client/js/components/Admin/SlackIntegration/CustomBotWithProxySettings.jsx

@@ -15,7 +15,7 @@ const CustomBotWithProxySettings = (props) => {
   return (
     <>
 
-      <h2 className="admin-setting-header">{t('admin:slack_integration.custom_bot_with_proxy_integration')}</h2>
+      <h2 className="admin-setting-header mb-2">{t('admin:slack_integration.custom_bot_with_proxy_integration')}</h2>
 
       {/* TODO delete tmp props */}
       <CustomBotWithProxyIntegrationCard
@@ -23,9 +23,19 @@ const CustomBotWithProxySettings = (props) => {
         slackWorkSpaceNames={['wsName1', 'wsName2']}
         isSlackScopeSet
       />
-
-      <div className="my-5 mx-3">
-        <CustomBotWithProxySettingsAccordion />
+      <h2 className="admin-setting-header">{t('admin:slack_integration.cooperation_procedure')}</h2>
+      <div className="mx-3">
+        <div className="d-flex flex-column pull-right">
+          <button
+            className="my-3 btn btn-outline-danger"
+            type="button"
+          ><i className="icon-trash mr-1" />{t('admin:slack_integration.delete')}
+          </button>
+        </div>
+
+        <div className="d-flex flex-column my-5 w-100">
+          <CustomBotWithProxySettingsAccordion />
+        </div>
       </div>
     </>
   );

+ 163 - 5
src/client/js/components/Admin/SlackIntegration/CustomBotWithProxySettingsAccordion.jsx

@@ -1,9 +1,38 @@
-import React from 'react';
+import React, { useState } from 'react';
 import { useTranslation } from 'react-i18next';
+import AdminUpdateButtonRow from '../Common/AdminUpdateButtonRow';
 import Accordion from '../Common/Accordion';
 
 const CustomBotWithProxySettingsAccordion = () => {
+  const [testChannel, setTestChannel] = useState('');
+  /* eslint-disable no-unused-vars */
+  // TODO: Add connection Logs
+  const [connectionErrorCode, setConnectionErrorCode] = useState(null);
+  const [connectionErrorMessage, setConnectionErrorMessage] = useState(null);
+  const [connectionSuccessMessage, setConnectionSuccessMessage] = useState(null);
+
   const { t } = useTranslation();
+
+  // TODO: Handle test button
+  const submitForm = (e) => {
+    e.preventDefault();
+    // eslint-disable-next-line no-console
+    console.log('Form Submitted');
+  };
+
+  const inputTestChannelHandler = (channel) => {
+    setTestChannel(channel);
+  };
+
+  // TODO: Show test logs
+  let value = '';
+  if (connectionErrorMessage != null) {
+    value = [connectionErrorCode, connectionErrorMessage];
+  }
+  if (connectionSuccessMessage != null) {
+    value = connectionSuccessMessage;
+  }
+
   return (
     <div className="card border-0 rounded-lg shadow overflow-hidden">
       <Accordion
@@ -46,14 +75,143 @@ const CustomBotWithProxySettingsAccordion = () => {
         </div>
       </Accordion>
       <Accordion
-        title={<><span className="mr-2">③</span>Third Accordion</>}
+        title={(
+          <>
+            <span className="mr-2">③</span>
+            {t('admin:slack_integration.accordion.generate_access_token')}
+            {' / '}
+            {t('admin:slack_integration.accordion.register_for_growi_official_bot_proxy_service')}
+          </>
+        )}
+      >
+        <div className="py-4 px-5">
+          <p className="font-weight-bold">1. {t('admin:slack_integration.accordion.generate_access_token')}</p>
+          <div className="form-group row">
+            <label className="text-left text-md-right col-md-3 col-form-label">Access Token for GROWI</label>
+            <div className="col-md-6">
+              <input
+                className="form-control"
+                type="text"
+              />
+            </div>
+          </div>
+          <div className="form-group row">
+            <label className="text-left text-md-right col-md-3 col-form-label">Access Token for Proxy</label>
+            <div className="col-md-6">
+              <input
+                className="form-control"
+                type="text"
+              />
+            </div>
+          </div>
+
+          <div className="row my-3">
+            <div className="mx-auto">
+              <button type="button" className="btn btn-outline-secondary mx-2">{ t('admin:slack_integration.access_token_settings.discard') }</button>
+              <button type="button" className="btn btn-primary mx-2">{ t('admin:slack_integration.access_token_settings.generate') }</button>
+            </div>
+          </div>
+          <p className="font-weight-bold">2. {t('admin:slack_integration.accordion.register_for_growi_official_bot_proxy_service')}</p>
+          <div className="d-flex flex-column align-items-center">
+            <ol className="p-0">
+              <li><p className="ml-2">{t('admin:slack_integration.accordion.enter_growi_register_on_slack')}</p></li>
+              <li>
+                <p
+                  className="ml-2"
+                  // TODO: Add dynamic link
+                  // TODO: Copy to clipboard on click
+                  // TODO: Add logo
+                  // eslint-disable-next-line react/no-danger
+                  dangerouslySetInnerHTML={{ __html: t('admin:slack_integration.accordion.paste_growi_url') }}
+                />
+              </li>
+              <li>
+                <p
+                  className="ml-2"
+                  // eslint-disable-next-line react/no-danger
+                  dangerouslySetInnerHTML={{ __html: t('admin:slack_integration.accordion.enter_access_token_for_growi_and_proxy') }}
+                />
+              </li>
+            </ol>
+            {/* TODO: Insert photo */}
+            <div className="rounded border w-50 d-flex justify-content-center align-items-center" style={{ height: '15rem' }}>
+              <h1 className="text-muted">参考画像</h1>
+            </div>
+          </div>
+        </div>
+      </Accordion>
+      <Accordion
+        title={<><span className="mr-2">④</span>{t('admin:slack_integration.accordion.set_proxy_url_on_growi')}</>}
       >
-        3
+        <div className="p-4">
+          <p
+            className="text-center"
+            // eslint-disable-next-line react/no-danger
+            dangerouslySetInnerHTML={{ __html: t('admin:slack_integration.accordion.enter_proxy_url_and_update') }}
+          />
+          <div className="form-group row my-4">
+            <label className="text-left text-md-right col-md-3 col-form-label">Proxy URL</label>
+            <div className="col-md-6">
+              <input
+                className="form-control"
+                type="text"
+              />
+            </div>
+          </div>
+          <AdminUpdateButtonRow
+            disabled={false}
+            // TODO: Add Proxy URL submit logic
+            // eslint-disable-next-line no-console
+            onClick={() => console.log('Update')}
+          />
+        </div>
       </Accordion>
       <Accordion
-        title={<><span className="mr-2">④</span>Fourth Accordion</>}
+        title={<><span className="mr-2">⑤</span>{t('admin:slack_integration.accordion.test_connection')}</>}
       >
-        4
+        {/* TODO: Responsive */}
+        <p className="text-center m-4">{t('admin:slack_integration.accordion.test_connection_by_pressing_button')}</p>
+        <div className="d-flex justify-content-center">
+          <form className="form-row justify-content-center" onSubmit={e => submitForm(e)}>
+            <div className="input-group col-8">
+              <div className="input-group-prepend">
+                <span className="input-group-text" id="slack-channel-addon"><i className="fa fa-hashtag" /></span>
+              </div>
+              <input
+                className="form-control"
+                type="text"
+                value={testChannel}
+                placeholder="Slack Channel"
+                // TODO: Handle test button
+                onChange={e => inputTestChannelHandler(e.target.value)}
+              />
+            </div>
+            <button
+              type="submit"
+              className="btn btn-info mx-3 font-weight-bold"
+              disabled={testChannel.trim() === ''}
+            >Test
+            </button>
+          </form>
+        </div>
+        {connectionErrorMessage != null
+          && <p className="text-danger text-center my-4">{t('admin:slack_integration.accordion.error_check_logs_below')}</p>}
+        {connectionSuccessMessage != null
+          && <p className="text-info text-center my-4">{t('admin:slack_integration.accordion.send_message_to_slack_work_space')}</p>}
+        <form>
+          <div className="row my-3 justify-content-center">
+            <div className="form-group slack-connection-log col-md-4">
+              <label className="mb-1"><p className="border-info slack-connection-log-title pl-2 m-0">Logs</p></label>
+              <textarea
+                className="form-control card border-info slack-connection-log-body rounded-lg"
+                rows="5"
+                // TODO: Show test logs
+                value={value}
+                readOnly
+              />
+            </div>
+          </div>
+        </form>
       </Accordion>
     </div>
   );

+ 18 - 20
src/client/js/components/Admin/SlackIntegration/CustomBotWithoutProxyIntegrationCard.jsx

@@ -23,25 +23,24 @@ const CustomBotWithoutProxyIntegrationCard = (props) => {
       </div>
 
       <div className="text-center w-25">
-        {props.isSlackScopeSet && (
-          <div className="mt-5">
-            <p className="text-success small">
-              <i className="fa fa-check mr-1" />
-              {t('admin:slack_integration.integration_sentence.integration_successful')}
-            </p>
-            <hr className="align-self-center admin-border-success border-success"></hr>
-          </div>
-        )}
-        {!props.isSlackScopeSet && (
-          <div className="mt-4">
-            <small
-              className="text-secondary m-0"
-                  // eslint-disable-next-line react/no-danger
-              dangerouslySetInnerHTML={{ __html: t('admin:slack_integration.integration_sentence.integration_is_not_complete') }}
-            />
-            <hr className="align-self-center admin-border-danger border-danger"></hr>
-          </div>
-        )}
+        {/* TODO apply correct condition GW-5895 */}
+        <div className="mt-4">
+          <small
+            className="text-secondary m-0"
+                // eslint-disable-next-line react/no-danger
+            dangerouslySetInnerHTML={{ __html: t('admin:slack_integration.integration_sentence.integration_is_not_complete') }}
+          />
+          <hr className="align-self-center admin-border-danger border-danger"></hr>
+        </div>
+
+        <div className="mt-5">
+          <p className="text-success small">
+            <i className="fa fa-check mr-1" />
+            {t('admin:slack_integration.integration_sentence.integration_successful')}
+          </p>
+          <hr className="align-self-center admin-border-success border-success"></hr>
+        </div>
+
       </div>
 
       <div className="card rounded-lg shadow border-0 w-50 admin-bot-card mb-0">
@@ -57,7 +56,6 @@ const CustomBotWithoutProxyIntegrationCard = (props) => {
 CustomBotWithoutProxyIntegrationCard.propTypes = {
   siteName: PropTypes.string.isRequired,
   slackWSNameInWithoutProxy: PropTypes.string,
-  isSlackScopeSet: PropTypes.bool.isRequired,
 };
 
 export default CustomBotWithoutProxyIntegrationCard;

+ 29 - 3
src/client/js/components/Admin/SlackIntegration/CustomBotWithoutProxySettings.jsx

@@ -4,14 +4,31 @@ import PropTypes from 'prop-types';
 import AppContainer from '../../../services/AppContainer';
 import AdminAppContainer from '../../../services/AdminAppContainer';
 import { withUnstatedContainers } from '../../UnstatedUtils';
+import { toastSuccess, toastError } from '../../../util/apiNotification';
 import CustomBotWithoutProxySettingsAccordion, { botInstallationStep } from './CustomBotWithoutProxySettingsAccordion';
 import CustomBotWithoutProxyIntegrationCard from './CustomBotWithoutProxyIntegrationCard';
+import DeleteSlackBotSettingsModal from './DeleteSlackBotSettingsModal';
 
 const CustomBotWithoutProxySettings = (props) => {
   const { appContainer } = props;
   const { t } = useTranslation();
 
   const [siteName, setSiteName] = useState('');
+  const [isDeleteConfirmModalShown, setIsDeleteConfirmModalShown] = useState(false);
+
+  const deleteSlackSettingsHandler = async() => {
+    try {
+      await appContainer.apiv3.put('/slack-integration-settings/custom-bot-without-proxy', {
+        slackSigningSecret: '',
+        slackBotToken: '',
+        currentBotType: '',
+      });
+      toastSuccess('success');
+    }
+    catch (err) {
+      toastError(err);
+    }
+  };
 
   useEffect(() => {
     const siteName = appContainer.config.crowi.title;
@@ -25,17 +42,28 @@ const CustomBotWithoutProxySettings = (props) => {
       <CustomBotWithoutProxyIntegrationCard
         siteName={siteName}
         slackWSNameInWithoutProxy={props.slackWSNameInWithoutProxy}
-        isSlackScopeSet={props.isSlackScopeSet}
       />
 
       <h2 className="admin-setting-header">{t('admin:slack_integration.custom_bot_without_proxy_settings')}</h2>
 
+      <button
+        className="mx-3 pull-right btn text-danger border-danger"
+        type="button"
+        onClick={() => setIsDeleteConfirmModalShown(true)}
+      >{t('admin:slack_integration.reset')}
+      </button>
+
       <div className="my-5 mx-3">
         <CustomBotWithoutProxySettingsAccordion
           {...props}
           activeStep={botInstallationStep.CREATE_BOT}
         />
       </div>
+      <DeleteSlackBotSettingsModal
+        isOpen={isDeleteConfirmModalShown}
+        onClose={() => setIsDeleteConfirmModalShown(false)}
+        onClickDeleteButton={deleteSlackSettingsHandler}
+      />
     </>
   );
 };
@@ -50,8 +78,6 @@ CustomBotWithoutProxySettings.propTypes = {
   slackBotToken: PropTypes.string,
   slackBotTokenEnv: PropTypes.string,
   isRgisterSlackCredentials: PropTypes.bool,
-  isConnectedToSlack: PropTypes.bool,
-  isSlackScopeSet: PropTypes.bool,
   slackWSNameInWithoutProxy: PropTypes.string,
 };
 

+ 17 - 17
src/client/js/components/Admin/SlackIntegration/CustomBotWithoutProxySettingsAccordion.jsx

@@ -34,7 +34,7 @@ const CustomBotWithoutProxySettingsAccordion = ({
 
   const updateSecretTokenHandler = async() => {
     try {
-      await appContainer.apiv3.put('/slack-integration-legacy/custom-bot-without-proxy', {
+      await appContainer.apiv3.put('/slack-integration-settings/custom-bot-without-proxy', {
         slackSigningSecret,
         slackBotToken,
         currentBotType,
@@ -68,7 +68,7 @@ const CustomBotWithoutProxySettingsAccordion = ({
     setConnectionErrorMessage(null);
     setConnectionSuccessMessage(null);
     try {
-      const res = await appContainer.apiv3.post('/slack-integration-legacy/notification-test-to-slack-work-space', {
+      const res = await appContainer.apiv3.post('/slack-integration-settings/notification-test-to-slack-work-space', {
         channel: testChannel,
       });
       setConnectionSuccessMessage(res.data.message);
@@ -160,25 +160,25 @@ const CustomBotWithoutProxySettingsAccordion = ({
       >
         <p className="text-center m-4">{t('admin:slack_integration.accordion.test_connection_by_pressing_button')}</p>
         <div className="d-flex justify-content-center">
-          <form className="form-row align-items-center w-25" onSubmit={e => submitForm(e)}>
-            <div className="col-8 input-group-prepend">
-              <span className="input-group-text" id="slack-channel-addon"><i className="fa fa-hashtag" /></span>
+          <form className="form-row align-items-center" onSubmit={e => submitForm(e)}>
+            <div className="input-group col-8">
+              <div className="input-group-prepend">
+                <span className="input-group-text" id="slack-channel-addon"><i className="fa fa-hashtag" /></span>
+              </div>
               <input
-                className="form-control w-100"
+                className="form-control"
                 type="text"
                 value={testChannel}
                 placeholder="Slack Channel"
                 onChange={e => inputTestChannelHandler(e.target.value)}
               />
             </div>
-            <div className="col-4">
-              <button
-                type="submit"
-                className="btn btn-info mx-3 font-weight-bold"
-                disabled={testChannel.trim() === ''}
-              >Test
-              </button>
-            </div>
+            <button
+              type="submit"
+              className="btn btn-info mx-3 font-weight-bold"
+              disabled={testChannel.trim() === ''}
+            >Test
+            </button>
           </form>
         </div>
         {connectionErrorMessage != null
@@ -187,10 +187,11 @@ const CustomBotWithoutProxySettingsAccordion = ({
           && <p className="text-info text-center my-4">{t('admin:slack_integration.accordion.send_message_to_slack_work_space')}</p>}
         <form>
           <div className="row my-3 justify-content-center">
-            <div className="form-group slack-connection-log w-25">
-              <label className="mb-1"><p className="border-info slack-connection-log-title pl-2">Logs</p></label>
+            <div className="form-group slack-connection-log col-md-4">
+              <label className="mb-1"><p className="border-info slack-connection-log-title pl-2 m-0">Logs</p></label>
               <textarea
                 className="form-control card border-info slack-connection-log-body rounded-lg"
+                rows="5"
                 value={value}
                 readOnly
               />
@@ -212,7 +213,6 @@ CustomBotWithoutProxySettingsAccordion.propTypes = {
   slackBotTokenEnv: PropTypes.string,
   isRegisterSlackCredentials: PropTypes.bool,
   isSendTestMessage: PropTypes.bool,
-  isConnectedToSlack: PropTypes.bool,
   fetchSlackIntegrationData: PropTypes.func,
   onSetSlackSigningSecret: PropTypes.func,
   onSetSlackBotToken: PropTypes.func,

+ 65 - 0
src/client/js/components/Admin/SlackIntegration/DeleteSlackBotSettingsModal.jsx

@@ -0,0 +1,65 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+
+import { withTranslation } from 'react-i18next';
+
+import {
+  Button, Modal, ModalHeader, ModalBody, ModalFooter,
+} from 'reactstrap';
+
+const DeleteSlackBotSettingsModal = React.memo((props) => {
+  const { t } = props;
+
+  function closeModal() {
+    if (props.onClose == null) {
+      return;
+    }
+
+    props.onClose();
+  }
+
+  function deleteSlackCredentialsHandler() {
+    if (props.onClickDeleteButton == null) {
+      return;
+    }
+    props.onClickDeleteButton();
+
+    closeModal();
+  }
+
+  function closeButtonHandler() {
+    closeModal();
+  }
+
+  return (
+    <Modal isOpen={props.isOpen} toggle={closeButtonHandler} className="page-comment-delete-modal">
+      <ModalHeader tag="h4" toggle={closeButtonHandler} className="bg-danger text-light">
+        <span>
+          <i className="icon-fw icon-fire"></i>
+          {t('admin:slack_integration.delete_slackbot_settings')}
+        </span>
+      </ModalHeader>
+      <ModalBody>
+        {t('admin:slack_integration.slackbot_settings_notice')}
+      </ModalBody>
+      <ModalFooter>
+        <Button onClick={closeButtonHandler}>{t('Cancel')}</Button>
+        <Button color="danger" onClick={deleteSlackCredentialsHandler}>
+          <i className="icon icon-fire"></i>
+          {t('Reset')}
+        </Button>
+      </ModalFooter>
+    </Modal>
+  );
+
+});
+
+DeleteSlackBotSettingsModal.propTypes = {
+  t: PropTypes.func.isRequired, // i18next
+
+  isOpen: PropTypes.bool.isRequired,
+  onClose: PropTypes.func,
+  onClickDeleteButton: PropTypes.func,
+};
+
+export default withTranslation()(DeleteSlackBotSettingsModal);

+ 81 - 6
src/client/js/components/Admin/SlackIntegration/OfficialbotSettingsAccordion.jsx

@@ -1,9 +1,16 @@
 import React from 'react';
+import PropTypes from 'prop-types';
 import { useTranslation } from 'react-i18next';
+import { withUnstatedContainers } from '../../UnstatedUtils';
 import Accordion from '../Common/Accordion';
+import AdminUpdateButtonRow from '../Common/AdminUpdateButtonRow';
+import AppContainer from '../../../services/AppContainer';
 
-const OfficialBotSettingsAccordion = () => {
+
+const OfficialBotSettingsAccordion = (props) => {
   const { t } = useTranslation();
+  const { appContainer } = props;
+  const growiUrl = appContainer.config.crowi.url;
 
   return (
     <div className="card border-0 rounded-lg shadow overflow-hidden">
@@ -30,14 +37,73 @@ const OfficialBotSettingsAccordion = () => {
       <Accordion
         title={<><span className="mr-2">②</span>{t('admin:slack_integration.accordion.register_official_bot_proxy_service')}</>}
       >
-        {/* TODO: GW-5824 add accordion contents  */}
-        hoge
+        <div className="py-4 px-5">
+          <p className="font-weight-bold">1. Access Tokenの発行</p>
+          <div className="form-group row">
+            <label className="text-left text-md-right col-md-3 col-form-label">Access Token for GROWI</label>
+            <div className="col-md-6">
+              <input
+                className="form-control"
+                type="text"
+              />
+            </div>
+          </div>
+          <div className="form-group row">
+            <label className="text-left text-md-right col-md-3 col-form-label">Access Token for Proxy</label>
+            <div className="col-md-6">
+              <input
+                className="form-control"
+                type="text"
+              />
+            </div>
+          </div>
+          <div className="row my-3">
+            <div className="mx-auto">
+              <button type="button" className="btn btn-outline-secondary mx-2">破棄</button>
+              <button type="button" className="btn btn-primary mx-2">{ t('Update') }</button>
+            </div>
+          </div>
+          <p className="font-weight-bold">2. GROWI Official Bot Proxy サービスへの登録</p>
+          <div className="d-flex flex-column align-items-center">
+            <ol className="p-0">
+              <li><p className="ml-2">Slack上で`/growi register`と打つ</p></li>
+              {/* TODO: Copy to clipboard on click by GW5856 */}
+              <li>
+                <p className="ml-2"><b>GROWI URL</b>には{growiUrl}
+                  <i className="fa fa-clipboard mx-1 text-secondary" aria-hidden="true"></i>
+                  を貼り付ける
+                </p>
+              </li>
+              <li><p className="ml-2">上記で発行した<b>Access Token for GROWI と Access Token for Proxy</b>を入れる</p></li>
+            </ol>
+            {/* TODO: Insert photo by GW5857 */}
+            <div className="rounded border w-50 d-flex justify-content-center align-items-center" style={{ height: '15rem' }}>
+              <h1 className="text-muted">参考画像</h1>
+            </div>
+          </div>
+        </div>
       </Accordion>
       <Accordion
         title={<><span className="mr-2">③</span>{t('admin:slack_integration.accordion.register_proxy_url')}</>}
       >
-        {/* TODO: GW-5824 add accordion contents  */}
-        hoge
+        <div className="p-4">
+          <p className="text-center">Slack上に通知された<b>Proxy URL</b>を入力し、更新してください。</p>
+          <div className="form-group row my-4">
+            <label className="text-left text-md-right col-md-3 col-form-label">Proxy URL</label>
+            <div className="col-md-6">
+              <input
+                className="form-control"
+                type="text"
+              />
+            </div>
+          </div>
+          <AdminUpdateButtonRow
+            disabled={false}
+            // TODO: Add Proxy URL submit logic
+            // eslint-disable-next-line no-console
+            onClick={() => console.log('Update')}
+          />
+        </div>
       </Accordion>
       <Accordion
         title={<><span className="mr-2">④</span>{t('admin:slack_integration.accordion.test_connection')}</>}
@@ -78,4 +144,13 @@ const OfficialBotSettingsAccordion = () => {
   );
 };
 
-export default OfficialBotSettingsAccordion;
+/**
+ * Wrapper component for using unstated
+ */
+const OfficialBotSettingsAccordionWrapper = withUnstatedContainers(OfficialBotSettingsAccordion, [AppContainer]);
+
+OfficialBotSettingsAccordion.propTypes = {
+  appContainer: PropTypes.instanceOf(AppContainer).isRequired,
+};
+
+export default OfficialBotSettingsAccordionWrapper;

+ 5 - 28
src/client/js/components/Admin/SlackIntegration/SlackIntegration.jsx

@@ -22,40 +22,33 @@ const SlackIntegration = (props) => {
   const [slackBotToken, setSlackBotToken] = useState(null);
   const [slackSigningSecretEnv, setSlackSigningSecretEnv] = useState('');
   const [slackBotTokenEnv, setSlackBotTokenEnv] = useState('');
-  const [isConnectedToSlack, setIsConnectedToSlack] = useState(false);
   const [isRegisterSlackCredentials, setIsRegisterSlackCredentials] = useState(false);
   const [isSendTestMessage, setIsSendTestMessage] = useState(false);
   const [slackWSNameInWithoutProxy, setSlackWSNameInWithoutProxy] = useState(null);
-  const [isSlackScopeSet, setIsSlackScopeSet] = useState(false);
 
   const fetchSlackWorkSpaceNameInWithoutProxy = useCallback(async() => {
-    if (!isConnectedToSlack) {
-      return setSlackWSNameInWithoutProxy(null);
-    }
+
     try {
-      const res = await appContainer.apiv3.get('/slack-integration-legacy/custom-bot-without-proxy/slack-workspace-name');
+      const res = await appContainer.apiv3.get('/slack-integration-settings/custom-bot-without-proxy/slack-workspace-name');
       setSlackWSNameInWithoutProxy(res.data.slackWorkSpaceName);
-      setIsSlackScopeSet(true);
     }
     catch (err) {
       if (err[0].message === 'missing_scope') {
         setSlackWSNameInWithoutProxy(null);
-        setIsSlackScopeSet(false);
         toastError(err, t('admin:slack_integration.set_scope'));
       }
       else {
         toastError(err);
       }
     }
-  }, [appContainer.apiv3, isConnectedToSlack, t]);
+  }, [appContainer.apiv3, t]);
 
   const fetchSlackIntegrationData = useCallback(async() => {
     try {
-      const response = await appContainer.apiv3.get('/slack-integration-legacy/');
+      const response = await appContainer.apiv3.get('/slack-integration-settings');
       const { currentBotType, customBotWithoutProxySettings } = response.data.slackBotSettingParams;
       const {
         slackSigningSecret, slackBotToken, slackSigningSecretEnvVars, slackBotTokenEnvVars,
-        isConnectedToSlack,
       } = customBotWithoutProxySettings;
 
       setCurrentBotType(currentBotType);
@@ -63,18 +56,7 @@ const SlackIntegration = (props) => {
       setSlackBotToken(slackBotToken);
       setSlackSigningSecretEnv(slackSigningSecretEnvVars);
       setSlackBotTokenEnv(slackBotTokenEnvVars);
-      setIsConnectedToSlack(isConnectedToSlack);
-
       fetchSlackWorkSpaceNameInWithoutProxy();
-
-      if (isConnectedToSlack) {
-        setIsRegisterSlackCredentials(true);
-      }
-      else {
-        setIsRegisterSlackCredentials(false);
-        setIsSendTestMessage(false);
-      }
-
     }
     catch (err) {
       toastError(err);
@@ -103,7 +85,7 @@ const SlackIntegration = (props) => {
 
   const changeCurrentBotSettingsHandler = async() => {
     try {
-      const res = await appContainer.apiv3.put('/slack-integration-legacy/custom-bot-without-proxy', {
+      const res = await appContainer.apiv3.put('/slack-integration-settings/custom-bot-without-proxy', {
         slackSigningSecret: '',
         slackBotToken: '',
         currentBotType: selectedBotType,
@@ -112,13 +94,10 @@ const SlackIntegration = (props) => {
       setSelectedBotType(null);
       toastSuccess(t('admin:slack_integration.bot_reset_successful'));
       setIsRegisterSlackCredentials(false);
-      setIsConnectedToSlack(false);
       setSlackSigningSecret(null);
       setSlackBotToken(null);
-      setIsConnectedToSlack(false);
       setIsSendTestMessage(false);
       setSlackWSNameInWithoutProxy(null);
-      setIsSlackScopeSet(false);
     }
     catch (err) {
       toastError(err);
@@ -136,8 +115,6 @@ const SlackIntegration = (props) => {
         <CustomBotWithoutProxySettings
           isSendTestMessage={isSendTestMessage}
           isRegisterSlackCredentials={isRegisterSlackCredentials}
-          isConnectedToSlack={isConnectedToSlack}
-          isSlackScopeSet={isSlackScopeSet}
           slackBotTokenEnv={slackBotTokenEnv}
           slackBotToken={slackBotToken}
           slackSigningSecretEnv={slackSigningSecretEnv}

+ 0 - 1
src/client/styles/scss/_admin.scss

@@ -114,7 +114,6 @@ $slack-work-space-name-card-border: #efc1f6;
       border-left: 2px solid;
     }
     .slack-connection-log-body {
-      min-height: 6rem;
       border: 2px solid;
     }
   }

+ 1 - 0
src/server/models/index.js

@@ -18,4 +18,5 @@ module.exports = {
   GlobalNotificationMailSetting: require('./GlobalNotificationSetting/GlobalNotificationMailSetting'),
   GlobalNotificationSlackSetting: require('./GlobalNotificationSetting/GlobalNotificationSlackSetting'),
   ShareLink: require('./share-link'),
+  SlackAppIntegration: require('./slack-app-integration'),
 };

+ 10 - 0
src/server/models/slack-app-integration.js

@@ -0,0 +1,10 @@
+module.exports = function(crowi) {
+  const mongoose = require('mongoose');
+
+  const slackAppIntegrationSchema = new mongoose.Schema({
+    tokenGtoP: { type: String, required: true, unique: true },
+    tokenPtoG: { type: String, required: true, unique: true },
+  });
+
+  return mongoose.model('SlackAppIntegration', slackAppIntegrationSchema);
+};

+ 1 - 1
src/server/routes/apiv3/index.js

@@ -47,7 +47,7 @@ module.exports = (crowi) => {
   router.use('/attachment', require('./attachment')(crowi));
 
   router.use('/slack-integration', require('./slack-integration')(crowi));
-  router.use('/slack-integration-legacy', require('./slack-integration-legacy')(crowi));
+  router.use('/slack-integration-settings', require('./slack-integration-settings')(crowi));
   router.use('/staffs', require('./staffs')(crowi));
 
   return router;

+ 0 - 0
src/server/routes/apiv3/slack-integration-legacy.js → src/server/routes/apiv3/slack-integration-settings.js


+ 17 - 4
src/server/service/slackbot.js

@@ -3,7 +3,7 @@ const mongoose = require('mongoose');
 
 const PAGINGLIMIT = 10;
 
-const { WebClient, LogLevel } = require('@slack/web-api');
+const { generateWebClient } = require('@growi/slack');
 
 const S2sMessage = require('../models/vo/s2s-message');
 const S2sMessageHandlable = require('./s2s-messaging/handlable');
@@ -28,10 +28,23 @@ class SlackBotService extends S2sMessageHandlable {
 
   async initialize() {
     this.isConnectedToSlack = false;
-    const token = this.crowi.configManager.getConfig('crowi', 'slackbot:token');
+    const currentBotType = this.crowi.configManager.getConfig('crowi', 'slackbot:currentBotType');
 
-    if (token != null) {
-      this.client = new WebClient(token, { logLevel: LogLevel.DEBUG });
+    if (currentBotType != null) {
+      let serverUri;
+      let token;
+
+      // connect to proxy
+      if (currentBotType !== 'customBotWithoutProxy') {
+        // TODO: https://youtrack.weseek.co.jp/issue/GW-5896
+        serverUri = 'http://localhost:8080/slack-api-proxy/';
+      }
+      // connect directly
+      else {
+        token = this.crowi.configManager.getConfig('crowi', 'slackbot:token');
+      }
+
+      this.client = generateWebClient(token, serverUri);
       logger.debug('SlackBot: setup is done');
       await this.sendAuthTest();
     }

+ 6 - 313
yarn.lock

@@ -2679,27 +2679,6 @@
   dependencies:
     type-detect "4.0.8"
 
-"@slack/bolt@^3.3.0":
-  version "3.3.0"
-  resolved "https://registry.yarnpkg.com/@slack/bolt/-/bolt-3.3.0.tgz#9bfb9252091f845ab20cac20d6801edae794a169"
-  integrity sha512-rG9OHszzbJB8ZQHUl8v/IR22OodOhx+jGUGm0ZEb0kKmISUNuOGY7FLbPav4soafQg+fKfj4sWtDNfXesZrDag==
-  dependencies:
-    "@slack/logger" "^3.0.0"
-    "@slack/oauth" "^2.0.0"
-    "@slack/socket-mode" "^1.0.0"
-    "@slack/types" "^2.0.0"
-    "@slack/web-api" "^6.0.0"
-    "@types/express" "^4.16.1"
-    "@types/node" ">=12"
-    "@types/promise.allsettled" "^1.0.3"
-    "@types/tsscmp" "^1.0.0"
-    axios "^0.21.1"
-    express "^4.16.4"
-    please-upgrade-node "^3.2.0"
-    promise.allsettled "^1.0.2"
-    raw-body "^2.3.3"
-    tsscmp "^1.0.6"
-
 "@slack/events-api@^3.0.0":
   version "3.0.0"
   resolved "https://registry.yarnpkg.com/@slack/events-api/-/events-api-3.0.0.tgz#3e7626ceb5700cb1cce2fbfe6e1583c23a3626fd"
@@ -2725,14 +2704,7 @@
   dependencies:
     "@types/node" ">=8.9.0"
 
-"@slack/logger@^3.0.0":
-  version "3.0.0"
-  resolved "https://registry.yarnpkg.com/@slack/logger/-/logger-3.0.0.tgz#b736d4e1c112c22a10ffab0c2d364620aedcb714"
-  integrity sha512-DTuBFbqu4gGfajREEMrkq5jBhcnskinhr4+AnfJEk48zhVeEv3XnUKGIX98B74kxhYsIMfApGGySTn7V3b5yBA==
-  dependencies:
-    "@types/node" ">=12.0.0"
-
-"@slack/oauth@^2.0.0", "@slack/oauth@^2.0.1":
+"@slack/oauth@^2.0.1":
   version "2.0.1"
   resolved "https://registry.yarnpkg.com/@slack/oauth/-/oauth-2.0.1.tgz#56f8f3cd45258465e2c45860f1ca60e307126e30"
   integrity sha512-Htiwa70u+uZuWNvYvMjCUuALTl7hMb/1v0sQhrXDDY0dh9tWWUxZCvL6dAR6pxqMCXMjhS3j+tq4o157SGVhRg==
@@ -2744,32 +2716,11 @@
     jsonwebtoken "^8.5.1"
     lodash.isstring "^4.0.1"
 
-"@slack/socket-mode@^1.0.0":
-  version "1.0.2"
-  resolved "https://registry.yarnpkg.com/@slack/socket-mode/-/socket-mode-1.0.2.tgz#c805a627aa6528b888ad236872d082b40ba4247a"
-  integrity sha512-Rk5FyfZrSXra5xi8u1ZhnzzqOMUcgCcIxTHP1CqnRPymrrC0j1QqzKh7uhQLw1L4Ah9PKs3ObiJe3rXQJtAd6g==
-  dependencies:
-    "@slack/logger" "^3.0.0"
-    "@slack/web-api" "^6.0.0"
-    "@types/node" ">=12.0.0"
-    "@types/p-queue" "^2.3.2"
-    "@types/ws" "^7.2.5"
-    eventemitter3 "^3.1.0"
-    finity "^0.5.4"
-    p-cancelable "^1.1.0"
-    p-queue "^2.4.2"
-    ws "^7.3.1"
-
 "@slack/types@^1.7.0":
   version "1.10.0"
   resolved "https://registry.yarnpkg.com/@slack/types/-/types-1.10.0.tgz#cbf7d83e1027f4cbfd13d6b429f120c7fb09127a"
   integrity sha512-tA7GG7Tj479vojfV3AoxbckalA48aK6giGjNtgH6ihpLwTyHE3fIgRrvt8TWfLwW8X8dyu7vgmAsGLRG7hWWOg==
 
-"@slack/types@^2.0.0":
-  version "2.0.0"
-  resolved "https://registry.yarnpkg.com/@slack/types/-/types-2.0.0.tgz#7b938ab576cd1d6c9ff9ad67a96f8058d101af10"
-  integrity sha512-Nu4jWC39mDY5egAX4oElwOypdu8Cx9tmR7bo3ghaHYaC7mkKM1+b+soanW5s2ssu4yOLxMdFExMh6wlR34B6CA==
-
 "@slack/web-api@^5.7.0":
   version "5.15.0"
   resolved "https://registry.yarnpkg.com/@slack/web-api/-/web-api-5.15.0.tgz#6bcf1d0a833c0e87e45150c2fd1f9657e3ec0b0b"
@@ -2786,7 +2737,7 @@
     p-queue "^6.6.1"
     p-retry "^4.0.0"
 
-"@slack/web-api@^6.0.0", "@slack/web-api@^6.1.0":
+"@slack/web-api@^6.1.0":
   version "6.1.0"
   resolved "https://registry.yarnpkg.com/@slack/web-api/-/web-api-6.1.0.tgz#27a17f61eb72100d6722ff17f581349c41d19b5f"
   integrity sha512-9MVHw+rDBaFvkvzm8lDNH/nlkvJCDKRIjFGMdpbyZlVLsm4rcht4qyiL71bqdyLATHXJnWknb/sl0FQGLLobIA==
@@ -2991,7 +2942,7 @@
     "@types/qs" "*"
     "@types/range-parser" "*"
 
-"@types/express@*", "@types/express@^4.16.1", "@types/express@^4.17.0", "@types/express@^4.17.11":
+"@types/express@*", "@types/express@^4.17.0", "@types/express@^4.17.11":
   version "4.17.11"
   resolved "https://registry.yarnpkg.com/@types/express/-/express-4.17.11.tgz#debe3caa6f8e5fcda96b47bd54e2f40c4ee59545"
   integrity sha512-no+R6rW60JEc59977wIxreQVsIEOAYwgCqldrA/vkpCnbD7MqTefO97lmoBe4WE0F156bC4uLSP1XHDOySnChg==
@@ -3135,11 +3086,6 @@
   resolved "https://registry.yarnpkg.com/@types/normalize-package-data/-/normalize-package-data-2.4.0.tgz#e486d0d97396d79beedd0a6e33f4534ff6b4973e"
   integrity sha512-f5j5b/Gf71L+dbqxIpQ4Z2WlmI/mPJ0fOkGGmFgtb6sAu97EPczzbS3/tJKxmcYDj55OX6ssqwDAWOHIYDRDGA==
 
-"@types/p-queue@^2.3.2":
-  version "2.3.2"
-  resolved "https://registry.yarnpkg.com/@types/p-queue/-/p-queue-2.3.2.tgz#16bc5fece69ef85efaf2bce8b13f3ebe39c5a1c8"
-  integrity sha512-eKAv5Ql6k78dh3ULCsSBxX6bFNuGjTmof5Q/T6PiECDq0Yf8IIn46jCyp3RJvCi8owaEmm3DZH1PEImjBMd/vQ==
-
 "@types/parse-json@^4.0.0":
   version "4.0.0"
   resolved "https://registry.yarnpkg.com/@types/parse-json/-/parse-json-4.0.0.tgz#2f8bb441434d163b35fb8ffdccd7138927ffb8c0"
@@ -3151,11 +3097,6 @@
   dependencies:
     "@types/express" "*"
 
-"@types/promise.allsettled@^1.0.3":
-  version "1.0.3"
-  resolved "https://registry.yarnpkg.com/@types/promise.allsettled/-/promise.allsettled-1.0.3.tgz#6f3166618226a570b98c8250fc78687a912e56d5"
-  integrity sha512-b/IFHHTkYkTqu41IH9UtpICwqrpKj2oNlb4KHPzFQDMiz+h1BgAeATeO0/XTph4+UkH9W2U0E4B4j64KWOovag==
-
 "@types/prop-types@*":
   version "15.7.3"
   resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.3.tgz#2ab0d5da2e5815f94b0b9d4b95d1e5f243ab2ca7"
@@ -3230,11 +3171,6 @@
   resolved "https://registry.yarnpkg.com/@types/strip-json-comments/-/strip-json-comments-0.0.30.tgz#9aa30c04db212a9a0649d6ae6fd50accc40748a1"
   integrity sha512-7NQmHra/JILCd1QqpSzl8+mJRc8ZHz3uDm8YV1Ks9IhK0epEiTw8aIErbvH9PI+6XbqhyIQy3462nEsn7UVzjQ==
 
-"@types/tsscmp@^1.0.0":
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/@types/tsscmp/-/tsscmp-1.0.0.tgz#761c885a530f9673ae6fda0cae38253ffd46cba6"
-  integrity sha512-rj18XR6c4Ohds86Lq8MI1NMRrXes4eLo4H06e5bJyKucE1rXGsfBBbFGD2oDC+DSufQCpnU3TTW7QAiwLx+7Yw==
-
 "@types/unist@*", "@types/unist@^2.0.0", "@types/unist@^2.0.2":
   version "2.0.3"
   resolved "https://registry.yarnpkg.com/@types/unist/-/unist-2.0.3.tgz#9c088679876f374eb5983f150d4787aa6fb32d7e"
@@ -3256,13 +3192,6 @@
     "@types/unist" "*"
     "@types/vfile-message" "*"
 
-"@types/ws@^7.2.5":
-  version "7.4.1"
-  resolved "https://registry.yarnpkg.com/@types/ws/-/ws-7.4.1.tgz#49eacb15a0534663d53a36fbf5b4d98f5ae9a73a"
-  integrity sha512-ISCK1iFnR+jYv7+jLNX0wDqesZ/5RAeY3wUx6QaphmocphU61h+b+PHjS18TF4WIPTu/MMzxIq2PHr32o2TS5Q==
-  dependencies:
-    "@types/node" "*"
-
 "@types/yargs-parser@*":
   version "15.0.0"
   resolved "https://registry.yarnpkg.com/@types/yargs-parser/-/yargs-parser-15.0.0.tgz#cb3f9f741869e20cce330ffbeb9271590483882d"
@@ -4015,17 +3944,6 @@ array.prototype.flatmap@^1.2.2:
     es-abstract "^1.15.0"
     function-bind "^1.1.1"
 
-array.prototype.map@^1.0.3:
-  version "1.0.3"
-  resolved "https://registry.yarnpkg.com/array.prototype.map/-/array.prototype.map-1.0.3.tgz#1609623618d3d84134a37d4a220030c2bd18420b"
-  integrity sha512-nNcb30v0wfDyIe26Yif3PcV1JXQp4zEeEfupG7L4SRjnD6HLbO5b2a7eVSba53bOx4YCHYMBHt+Fp4vYstneRA==
-  dependencies:
-    call-bind "^1.0.0"
-    define-properties "^1.1.3"
-    es-abstract "^1.18.0-next.1"
-    es-array-method-boxes-properly "^1.0.0"
-    is-string "^1.0.5"
-
 arraybuffer.slice@~0.0.7:
   version "0.0.7"
   resolved "https://registry.yarnpkg.com/arraybuffer.slice/-/arraybuffer.slice-0.0.7.tgz#3bbc4275dd584cc1b10809b89d4e8b63a69e7675"
@@ -7432,28 +7350,6 @@ es-abstract@^1.15.0:
     string.prototype.trimleft "^2.1.0"
     string.prototype.trimright "^2.1.0"
 
-es-abstract@^1.18.0-next.1, es-abstract@^1.18.0-next.2:
-  version "1.18.0"
-  resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.18.0.tgz#ab80b359eecb7ede4c298000390bc5ac3ec7b5a4"
-  integrity sha512-LJzK7MrQa8TS0ja2w3YNLzUgJCGPdPOV1yVvezjNnS89D+VR08+Szt2mz3YB2Dck/+w5tfIq/RoUAFqJJGM2yw==
-  dependencies:
-    call-bind "^1.0.2"
-    es-to-primitive "^1.2.1"
-    function-bind "^1.1.1"
-    get-intrinsic "^1.1.1"
-    has "^1.0.3"
-    has-symbols "^1.0.2"
-    is-callable "^1.2.3"
-    is-negative-zero "^2.0.1"
-    is-regex "^1.1.2"
-    is-string "^1.0.5"
-    object-inspect "^1.9.0"
-    object-keys "^1.1.1"
-    object.assign "^4.1.2"
-    string.prototype.trimend "^1.0.4"
-    string.prototype.trimstart "^1.0.4"
-    unbox-primitive "^1.0.0"
-
 es-abstract@^1.4.3:
   version "1.11.0"
   resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.11.0.tgz#cce87d518f0496893b1a30cd8461835535480681"
@@ -7484,25 +7380,6 @@ es-abstract@^1.7.0:
     is-callable "^1.1.3"
     is-regex "^1.0.4"
 
-es-array-method-boxes-properly@^1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/es-array-method-boxes-properly/-/es-array-method-boxes-properly-1.0.0.tgz#873f3e84418de4ee19c5be752990b2e44718d09e"
-  integrity sha512-wd6JXUmyHmt8T5a2xreUwKcGPq6f1f+WwIJkijUqiGcJz1qqnZgP6XIK+QyIWU5lT7imeNxUll48bziG+TSYcA==
-
-es-get-iterator@^1.0.2:
-  version "1.1.2"
-  resolved "https://registry.yarnpkg.com/es-get-iterator/-/es-get-iterator-1.1.2.tgz#9234c54aba713486d7ebde0220864af5e2b283f7"
-  integrity sha512-+DTO8GYwbMCwbywjimwZMHp8AuYXOS2JZFWoi2AlPOS3ebnII9w/NLpNZtA7A0YLaVDw+O7KFCeoIV7OPvM7hQ==
-  dependencies:
-    call-bind "^1.0.2"
-    get-intrinsic "^1.1.0"
-    has-symbols "^1.0.1"
-    is-arguments "^1.1.0"
-    is-map "^2.0.2"
-    is-set "^2.0.2"
-    is-string "^1.0.5"
-    isarray "^2.0.5"
-
 es-to-primitive@^1.1.1:
   version "1.1.1"
   resolved "https://registry.yarnpkg.com/es-to-primitive/-/es-to-primitive-1.1.1.tgz#45355248a88979034b6792e19bb81f2b7975dd0d"
@@ -7519,15 +7396,6 @@ es-to-primitive@^1.2.0:
     is-date-object "^1.0.1"
     is-symbol "^1.0.2"
 
-es-to-primitive@^1.2.1:
-  version "1.2.1"
-  resolved "https://registry.yarnpkg.com/es-to-primitive/-/es-to-primitive-1.2.1.tgz#e55cd4c9cdc188bcefb03b366c736323fc5c898a"
-  integrity sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==
-  dependencies:
-    is-callable "^1.1.4"
-    is-date-object "^1.0.1"
-    is-symbol "^1.0.2"
-
 es6-object-assign@^1.1.0:
   version "1.1.0"
   resolved "https://registry.yarnpkg.com/es6-object-assign/-/es6-object-assign-1.1.0.tgz#c2c3582656247c39ea107cb1e6652b6f9f24523c"
@@ -8028,7 +7896,7 @@ express-webpack-assets@^0.1.0:
   version "0.1.0"
   resolved "https://registry.yarnpkg.com/express-webpack-assets/-/express-webpack-assets-0.1.0.tgz#000fb3413eb0d512cbd6cd3f6a10b5e70dbe0079"
 
-express@^4.0.0, express@^4.16.4, express@^4.17.1:
+express@^4.0.0, express@^4.17.1:
   version "4.17.1"
   resolved "https://registry.yarnpkg.com/express/-/express-4.17.1.tgz#4491fc38605cf51f8629d39c2b5d026f98a4c134"
   integrity sha512-mHJ9O79RqluphRrcw2X/GTh3k9tVv8YcoyY4Kkh4WDMUYKRZUq0h1o0w2rrrxBqM7VoeUVqgb27xlEMXTnYt4g==
@@ -8502,11 +8370,6 @@ findup-sync@^4.0.0:
     micromatch "^4.0.2"
     resolve-dir "^1.0.1"
 
-finity@^0.5.4:
-  version "0.5.4"
-  resolved "https://registry.yarnpkg.com/finity/-/finity-0.5.4.tgz#f2a8a9198e8286467328ec32c8bfcc19a2229c11"
-  integrity sha512-3l+5/1tuw616Lgb0QBimxfdd2TqaDGpfCBpfX6EqtFmqUV3FtQnVEX4Aa62DagYEqnsTIjZcTfbq9msDbXYgyA==
-
 flat-cache@^2.0.1:
   version "2.0.1"
   resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-2.0.1.tgz#5d296d6f04bda44a4630a301413bdbc2ec085ec0"
@@ -8870,15 +8733,6 @@ get-intrinsic@^1.0.2:
     has "^1.0.3"
     has-symbols "^1.0.1"
 
-get-intrinsic@^1.1.0, get-intrinsic@^1.1.1:
-  version "1.1.1"
-  resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.1.1.tgz#15f59f376f855c446963948f0d24cd3637b4abc6"
-  integrity sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q==
-  dependencies:
-    function-bind "^1.1.1"
-    has "^1.0.3"
-    has-symbols "^1.0.1"
-
 get-pkg-repo@^1.0.0:
   version "1.4.0"
   resolved "https://registry.yarnpkg.com/get-pkg-repo/-/get-pkg-repo-1.4.0.tgz#c73b489c06d80cc5536c2c853f9e05232056972d"
@@ -9365,11 +9219,6 @@ has-ansi@^2.0.0:
   dependencies:
     ansi-regex "^2.0.0"
 
-has-bigints@^1.0.1:
-  version "1.0.1"
-  resolved "https://registry.yarnpkg.com/has-bigints/-/has-bigints-1.0.1.tgz#64fe6acb020673e3b78db035a5af69aa9d07b113"
-  integrity sha512-LSBS2LjbNBTf6287JEbEzvJgftkF5qFkmCo9hDRpAzKhUOlJ+hx8dd4USs00SgsUNwc4617J9ki5YtEClM2ffA==
-
 has-binary2@~1.0.2:
   version "1.0.2"
   resolved "https://registry.yarnpkg.com/has-binary2/-/has-binary2-1.0.2.tgz#e83dba49f0b9be4d026d27365350d9f03f54be98"
@@ -9403,7 +9252,7 @@ has-symbols@^1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.0.tgz#ba1a8f1af2a0fc39650f5c850367704122063b44"
 
-has-symbols@^1.0.1, has-symbols@^1.0.2:
+has-symbols@^1.0.1:
   version "1.0.2"
   resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.2.tgz#165d3070c00309752a1236a479331e3ac56f1423"
   integrity sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw==
@@ -10155,7 +10004,7 @@ is-alphanumerical@^1.0.0:
     is-alphabetical "^1.0.0"
     is-decimal "^1.0.0"
 
-is-arguments@^1.0.4, is-arguments@^1.1.0:
+is-arguments@^1.0.4:
   version "1.1.0"
   resolved "https://registry.yarnpkg.com/is-arguments/-/is-arguments-1.1.0.tgz#62353031dfbee07ceb34656a6bde59efecae8dd9"
   integrity sha512-1Ij4lOMPl/xB5kBDn7I+b2ttPMKa8szhEIrXDuXQD/oe3HJLTLhqhgGspwgyGd6MOywBUqVvYicF72lkgDnIHg==
@@ -10171,11 +10020,6 @@ is-arrayish@^0.3.1:
   version "0.3.2"
   resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.3.2.tgz#4574a2ae56f7ab206896fb431eaeed066fdf8f03"
 
-is-bigint@^1.0.1:
-  version "1.0.1"
-  resolved "https://registry.yarnpkg.com/is-bigint/-/is-bigint-1.0.1.tgz#6923051dfcbc764278540b9ce0e6b3213aa5ebc2"
-  integrity sha512-J0ELF4yHFxHy0cmSxZuheDOz2luOdVvqjwmEcj8H/L1JHeuEDSDbeRP+Dk9kFVk5RTFzbucJ2Kb9F7ixY2QaCg==
-
 is-binary-path@^1.0.0:
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-1.0.1.tgz#75f16642b480f187a711c814161fd3a4a7655898"
@@ -10189,13 +10033,6 @@ is-binary-path@~2.1.0:
   dependencies:
     binary-extensions "^2.0.0"
 
-is-boolean-object@^1.1.0:
-  version "1.1.0"
-  resolved "https://registry.yarnpkg.com/is-boolean-object/-/is-boolean-object-1.1.0.tgz#e2aaad3a3a8fca34c28f6eee135b156ed2587ff0"
-  integrity sha512-a7Uprx8UtD+HWdyYwnD1+ExtTgqQtD2k/1yJgtXP6wnMm8byhkoTZRl+95LLThpzNZJ5aEvi46cdH+ayMFRwmA==
-  dependencies:
-    call-bind "^1.0.0"
-
 is-buffer@^1.1.5, is-buffer@~1.1.1:
   version "1.1.6"
   resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be"
@@ -10213,11 +10050,6 @@ is-callable@^1.1.4:
   version "1.1.4"
   resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.1.4.tgz#1e1adf219e1eeb684d691f9d6a05ff0d30a24d75"
 
-is-callable@^1.2.3:
-  version "1.2.3"
-  resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.3.tgz#8b1e0500b73a1d76c70487636f368e519de8db8e"
-  integrity sha512-J1DcMe8UYTBSrKezuIUTUwjXsho29693unXM2YhJUTR2txK/eG47bvNa/wipPFmZFgr/N6f1GA66dv0mEyTIyQ==
-
 is-ci@^2.0.0:
   version "2.0.0"
   resolved "https://registry.yarnpkg.com/is-ci/-/is-ci-2.0.0.tgz#6bc6334181810e04b5c22b3d589fdca55026404c"
@@ -10384,27 +10216,12 @@ is-lambda@^1.0.1:
   resolved "https://registry.yarnpkg.com/is-lambda/-/is-lambda-1.0.1.tgz#3d9877899e6a53efc0160504cde15f82e6f061d5"
   integrity sha1-PZh3iZ5qU+/AFgUEzeFfgubwYdU=
 
-is-map@^2.0.2:
-  version "2.0.2"
-  resolved "https://registry.yarnpkg.com/is-map/-/is-map-2.0.2.tgz#00922db8c9bf73e81b7a335827bc2a43f2b91127"
-  integrity sha512-cOZFQQozTha1f4MxLFzlgKYPTyj26picdZTx82hbc/Xf4K/tZOOXSCkMvU4pKioRXGDLJRn0GM7Upe7kR721yg==
-
-is-negative-zero@^2.0.1:
-  version "2.0.1"
-  resolved "https://registry.yarnpkg.com/is-negative-zero/-/is-negative-zero-2.0.1.tgz#3de746c18dda2319241a53675908d8f766f11c24"
-  integrity sha512-2z6JzQvZRa9A2Y7xC6dQQm4FSTSTNWjKIYYTt4246eMTJmIo0Q+ZyOsU66X8lxK1AbB92dFeglPLrhwpeRKO6w==
-
 is-number-like@^1.0.3:
   version "1.0.8"
   resolved "https://registry.yarnpkg.com/is-number-like/-/is-number-like-1.0.8.tgz#2e129620b50891042e44e9bbbb30593e75cfbbe3"
   dependencies:
     lodash.isfinite "^3.3.2"
 
-is-number-object@^1.0.4:
-  version "1.0.4"
-  resolved "https://registry.yarnpkg.com/is-number-object/-/is-number-object-1.0.4.tgz#36ac95e741cf18b283fc1ddf5e83da798e3ec197"
-  integrity sha512-zohwelOAur+5uXtk8O3GPQ1eAcu4ZX3UwxQhUlfFFMNpUd83gXgjbhJh6HmB6LUNV/ieOLQuDwJO3dWJosUeMw==
-
 is-number@^2.1.0:
   version "2.1.0"
   resolved "https://registry.yarnpkg.com/is-number/-/is-number-2.1.0.tgz#01fcbbb393463a548f2f466cce16dece49db908f"
@@ -10485,14 +10302,6 @@ is-regex@^1.0.4:
   dependencies:
     has "^1.0.1"
 
-is-regex@^1.1.2:
-  version "1.1.2"
-  resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.1.2.tgz#81c8ebde4db142f2cf1c53fc86d6a45788266251"
-  integrity sha512-axvdhb5pdhEVThqJzYXwMlVuZwC+FF2DpcOhTS+y/8jVq4trxyPgfcwIxIKiyeuLlSQYKkmUaPQJ8ZE4yNKXDg==
-  dependencies:
-    call-bind "^1.0.2"
-    has-symbols "^1.0.1"
-
 is-regexp@^2.0.0:
   version "2.1.0"
   resolved "https://registry.yarnpkg.com/is-regexp/-/is-regexp-2.1.0.tgz#cd734a56864e23b956bf4e7c66c396a4c0b22c2d"
@@ -10507,11 +10316,6 @@ is-retry-allowed@^1.1.0:
   resolved "https://registry.yarnpkg.com/is-retry-allowed/-/is-retry-allowed-1.1.0.tgz#11a060568b67339444033d0125a61a20d564fb34"
   integrity sha1-EaBgVotnM5REAz0BJaYaINVk+zQ=
 
-is-set@^2.0.2:
-  version "2.0.2"
-  resolved "https://registry.yarnpkg.com/is-set/-/is-set-2.0.2.tgz#90755fa4c2562dc1c5d4024760d6119b94ca18ec"
-  integrity sha512-+2cnTEZeY5z/iXGbLhPrOAaK/Mau5k5eXq9j14CpRTftq0pAJu2MwVRSZhyZWBzx3o6X795Lz6Bpb6R0GKf37g==
-
 is-ssh@^1.3.0:
   version "1.3.2"
   resolved "https://registry.yarnpkg.com/is-ssh/-/is-ssh-1.3.2.tgz#a4b82ab63d73976fd8263cceee27f99a88bdae2b"
@@ -10528,11 +10332,6 @@ is-stream@^2.0.0:
   resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-2.0.0.tgz#bde9c32680d6fae04129d6ac9d921ce7815f78e3"
   integrity sha512-XCoy+WlUr7d1+Z8GgSuXmpuUFC9fOhRXglJMx+dwLKTkL44Cjd4W1Z5P+BQZpr+cR93aGP4S/s7Ftw6Nd/kiEw==
 
-is-string@^1.0.5:
-  version "1.0.5"
-  resolved "https://registry.yarnpkg.com/is-string/-/is-string-1.0.5.tgz#40493ed198ef3ff477b8c7f92f644ec82a5cd3a6"
-  integrity sha512-buY6VNRjhQMiF1qWDouloZlQbRhDPCebwxSjxMjxgemYT46YMd2NR0/H+fBhEfWX4A/w9TBJ+ol+okqJKFE6vQ==
-
 is-svg@^3.0.0:
   version "3.0.0"
   resolved "https://registry.yarnpkg.com/is-svg/-/is-svg-3.0.0.tgz#9321dbd29c212e5ca99c4fa9794c714bcafa2f75"
@@ -10549,13 +10348,6 @@ is-symbol@^1.0.2:
   dependencies:
     has-symbols "^1.0.0"
 
-is-symbol@^1.0.3:
-  version "1.0.3"
-  resolved "https://registry.yarnpkg.com/is-symbol/-/is-symbol-1.0.3.tgz#38e1014b9e6329be0de9d24a414fd7441ec61937"
-  integrity sha512-OwijhaRSgqvhm/0ZdAcXNZt9lYdKFpcRDT5ULUuYXPoT794UNOdU+gpT6Rzo7b4V2HUl/op6GqY894AZwv9faQ==
-  dependencies:
-    has-symbols "^1.0.1"
-
 is-text-path@^1.0.1:
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/is-text-path/-/is-text-path-1.0.1.tgz#4e1aa0fb51bfbcb3e92688001397202c1775b66e"
@@ -10615,11 +10407,6 @@ isarray@2.0.1:
   version "2.0.1"
   resolved "https://registry.yarnpkg.com/isarray/-/isarray-2.0.1.tgz#a37d94ed9cda2d59865c9f76fe596ee1f338741e"
 
-isarray@^2.0.5:
-  version "2.0.5"
-  resolved "https://registry.yarnpkg.com/isarray/-/isarray-2.0.5.tgz#8af1e4c1221244cc62459faf38940d4e644a5723"
-  integrity sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==
-
 isexe@^2.0.0:
   version "2.0.0"
   resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10"
@@ -10699,19 +10486,6 @@ isurl@^1.0.0-alpha5:
     has-to-string-tag-x "^1.2.0"
     is-object "^1.0.1"
 
-iterate-iterator@^1.0.1:
-  version "1.0.1"
-  resolved "https://registry.yarnpkg.com/iterate-iterator/-/iterate-iterator-1.0.1.tgz#1693a768c1ddd79c969051459453f082fe82e9f6"
-  integrity sha512-3Q6tudGN05kbkDQDI4CqjaBf4qf85w6W6GnuZDtUVYwKgtC1q8yxYX7CZed7N+tLzQqS6roujWvszf13T+n9aw==
-
-iterate-value@^1.0.2:
-  version "1.0.2"
-  resolved "https://registry.yarnpkg.com/iterate-value/-/iterate-value-1.0.2.tgz#935115bd37d006a52046535ebc8d07e9c9337f57"
-  integrity sha512-A6fMAio4D2ot2r/TYzr4yUWrmwNdsN5xL7+HUiyACE4DXm+q8HtPcnFTp+NnW3k4N05tZ7FVYFFb2CR13NxyHQ==
-  dependencies:
-    es-get-iterator "^1.0.2"
-    iterate-iterator "^1.0.1"
-
 jake@^10.6.1:
   version "10.8.2"
   resolved "https://registry.yarnpkg.com/jake/-/jake-10.8.2.tgz#ebc9de8558160a66d82d0eadc6a2e58fbc500a7b"
@@ -13699,16 +13473,6 @@ object.assign@^4.1.0:
     has-symbols "^1.0.0"
     object-keys "^1.0.11"
 
-object.assign@^4.1.2:
-  version "4.1.2"
-  resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.2.tgz#0ed54a342eceb37b38ff76eb831a0e788cb63940"
-  integrity sha512-ixT2L5THXsApyiUPYKmW+2EHpXXe5Ii3M+f4e+aJFAHao5amFRW6J0OO6c/LU8Be47utCx2GL89hxGB6XSmKuQ==
-  dependencies:
-    call-bind "^1.0.0"
-    define-properties "^1.1.3"
-    has-symbols "^1.0.1"
-    object-keys "^1.1.1"
-
 object.entries@^1.0.4, object.entries@^1.1.0:
   version "1.1.0"
   resolved "https://registry.yarnpkg.com/object.entries/-/object.entries-1.1.0.tgz#2024fc6d6ba246aee38bdb0ffd5cfbcf371b7519"
@@ -13939,11 +13703,6 @@ p-cancelable@^0.4.0:
   resolved "https://registry.yarnpkg.com/p-cancelable/-/p-cancelable-0.4.1.tgz#35f363d67d52081c8d9585e37bcceb7e0bbcb2a0"
   integrity sha512-HNa1A8LvB1kie7cERyy21VNeHb2CWJJYqyyC2o3klWFfMGlFmWv2Z7sFgZH8ZiaYL95ydToKTFVXgMV/Os0bBQ==
 
-p-cancelable@^1.1.0:
-  version "1.1.0"
-  resolved "https://registry.yarnpkg.com/p-cancelable/-/p-cancelable-1.1.0.tgz#d078d15a3af409220c886f1d9a0ca2e441ab26cc"
-  integrity sha512-s73XxOZ4zpt1edZYZzvhqFa6uvQc1vwUa0K0BdtIZgQMAJj9IbebH+JkgKZc9h+B05PKHLOTl4ajG1BmNrVZlw==
-
 p-defer@^1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/p-defer/-/p-defer-1.0.0.tgz#9f6eb182f6c9aa8cd743004a7d4f96b196b0fb0c"
@@ -14033,11 +13792,6 @@ p-pipe@^3.1.0:
   resolved "https://registry.yarnpkg.com/p-pipe/-/p-pipe-3.1.0.tgz#48b57c922aa2e1af6a6404cb7c6bf0eb9cc8e60e"
   integrity sha512-08pj8ATpzMR0Y80x50yJHn37NF6vjrqHutASaX5LiH5npS9XPvrUmscd9MF5R4fuYRHOxQR1FfMIlF7AzwoPqw==
 
-p-queue@^2.4.2:
-  version "2.4.2"
-  resolved "https://registry.yarnpkg.com/p-queue/-/p-queue-2.4.2.tgz#03609826682b743be9a22dba25051bd46724fc34"
-  integrity sha512-n8/y+yDJwBjoLQe1GSJbbaYQLTI7QHNZI2+rpmCDbe++WLf9HC3gf6iqj5yfPAV71W4UF3ql5W1+UBPXoXTxng==
-
 p-queue@^6.6.1, p-queue@^6.6.2:
   version "6.6.2"
   resolved "https://registry.yarnpkg.com/p-queue/-/p-queue-6.6.2.tgz#2068a9dcf8e67dd0ec3e7a2bcb76810faa85e426"
@@ -14605,13 +14359,6 @@ platform@1.3.5:
   version "1.3.5"
   resolved "https://registry.yarnpkg.com/platform/-/platform-1.3.5.tgz#fb6958c696e07e2918d2eeda0f0bc9448d733444"
 
-please-upgrade-node@^3.2.0:
-  version "3.2.0"
-  resolved "https://registry.yarnpkg.com/please-upgrade-node/-/please-upgrade-node-3.2.0.tgz#aeddd3f994c933e4ad98b99d9a556efa0e2fe942"
-  integrity sha512-gQR3WpIgNIKwBMVLkpMUeR3e1/E1y42bqDQZfql+kDeXd8COYfM8PQA4X6y7a8u9Ua9FHmsrrmirW2vHs45hWg==
-  dependencies:
-    semver-compare "^1.0.0"
-
 pluralize@^8.0.0:
   version "8.0.0"
   resolved "https://registry.yarnpkg.com/pluralize/-/pluralize-8.0.0.tgz#1a6fa16a38d12a1901e0320fa017051c539ce3b1"
@@ -15195,18 +14942,6 @@ promise-retry@^2.0.1:
     err-code "^2.0.2"
     retry "^0.12.0"
 
-promise.allsettled@^1.0.2:
-  version "1.0.4"
-  resolved "https://registry.yarnpkg.com/promise.allsettled/-/promise.allsettled-1.0.4.tgz#65e71f2a604082ed69c548b68603294090ee6803"
-  integrity sha512-o73CbvQh/OnPFShxHcHxk0baXR2a1m4ozb85ha0H14VEoi/EJJLa9mnPfEWJx9RjA9MLfhdjZ8I6HhWtBa64Ag==
-  dependencies:
-    array.prototype.map "^1.0.3"
-    call-bind "^1.0.2"
-    define-properties "^1.1.3"
-    es-abstract "^1.18.0-next.2"
-    get-intrinsic "^1.0.2"
-    iterate-value "^1.0.2"
-
 promise@^7.1.1:
   version "7.3.1"
   resolved "https://registry.yarnpkg.com/promise/-/promise-7.3.1.tgz#064b72602b18f90f29192b8b1bc418ffd1ebd3bf"
@@ -16871,11 +16606,6 @@ scss-tokenizer@^0.2.3:
     js-base64 "^2.1.8"
     source-map "^0.4.2"
 
-semver-compare@^1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/semver-compare/-/semver-compare-1.0.0.tgz#0dee216a1c941ab37e9efb1788f6afc5ff5537fc"
-  integrity sha1-De4hahyUGrN+nvsXiPavxf9VN/w=
-
 "semver@2 || 3 || 4 || 5", semver@^5.7.1:
   version "5.7.1"
   resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7"
@@ -17903,14 +17633,6 @@ string.prototype.padend@^3.0.0:
     es-abstract "^1.4.3"
     function-bind "^1.0.2"
 
-string.prototype.trimend@^1.0.4:
-  version "1.0.4"
-  resolved "https://registry.yarnpkg.com/string.prototype.trimend/-/string.prototype.trimend-1.0.4.tgz#e75ae90c2942c63504686c18b287b4a0b1a45f80"
-  integrity sha512-y9xCjw1P23Awk8EvTpcyL2NIr1j7wJ39f+k6lvRnSMz+mz9CGz9NYPelDk42kOz6+ql8xjfK8oYzy3jAP5QU5A==
-  dependencies:
-    call-bind "^1.0.2"
-    define-properties "^1.1.3"
-
 string.prototype.trimleft@^2.1.0:
   version "2.1.0"
   resolved "https://registry.yarnpkg.com/string.prototype.trimleft/-/string.prototype.trimleft-2.1.0.tgz#6cc47f0d7eb8d62b0f3701611715a3954591d634"
@@ -17927,14 +17649,6 @@ string.prototype.trimright@^2.1.0:
     define-properties "^1.1.3"
     function-bind "^1.1.1"
 
-string.prototype.trimstart@^1.0.4:
-  version "1.0.4"
-  resolved "https://registry.yarnpkg.com/string.prototype.trimstart/-/string.prototype.trimstart-1.0.4.tgz#b36399af4ab2999b4c9c648bd7a3fb2bb26feeed"
-  integrity sha512-jh6e984OBfvxS50tdY2nRZnoC5/mLFKOREQfw8t5yytkoUsJRNxvI/E39qu1sD0OtWI3OC0XgKSmcWwziwYuZw==
-  dependencies:
-    call-bind "^1.0.2"
-    define-properties "^1.1.3"
-
 string@^3.0.1:
   version "3.3.3"
   resolved "https://registry.yarnpkg.com/string/-/string-3.3.3.tgz#5ea211cd92d228e184294990a6cc97b366a77cb0"
@@ -19094,16 +18808,6 @@ umask@^1.1.0:
   resolved "https://registry.yarnpkg.com/umask/-/umask-1.1.0.tgz#f29cebf01df517912bb58ff9c4e50fde8e33320d"
   integrity sha1-8pzr8B31F5ErtY/5xOUP3o4zMg0=
 
-unbox-primitive@^1.0.0:
-  version "1.0.1"
-  resolved "https://registry.yarnpkg.com/unbox-primitive/-/unbox-primitive-1.0.1.tgz#085e215625ec3162574dc8859abee78a59b14471"
-  integrity sha512-tZU/3NqK3dA5gpE1KtyiJUrEB0lxnGkMFHptJ7q6ewdZ8s12QrODwNbhIJStmJkd1QDXa1NRA8aF2A1zk/Ypyw==
-  dependencies:
-    function-bind "^1.1.1"
-    has-bigints "^1.0.1"
-    has-symbols "^1.0.2"
-    which-boxed-primitive "^1.0.2"
-
 unherit@^1.0.4:
   version "1.1.2"
   resolved "https://registry.yarnpkg.com/unherit/-/unherit-1.1.2.tgz#14f1f397253ee4ec95cec167762e77df83678449"
@@ -19789,17 +19493,6 @@ when@^3.7.7:
   version "3.7.8"
   resolved "https://registry.yarnpkg.com/when/-/when-3.7.8.tgz#c7130b6a7ea04693e842cdc9e7a1f2aa39a39f82"
 
-which-boxed-primitive@^1.0.2:
-  version "1.0.2"
-  resolved "https://registry.yarnpkg.com/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz#13757bc89b209b049fe5d86430e21cf40a89a8e6"
-  integrity sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==
-  dependencies:
-    is-bigint "^1.0.1"
-    is-boolean-object "^1.1.0"
-    is-number-object "^1.0.4"
-    is-string "^1.0.5"
-    is-symbol "^1.0.3"
-
 which-module@^1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/which-module/-/which-module-1.0.0.tgz#bba63ca861948994ff307736089e3b96026c2a4f"