check-communicable.ts 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149
  1. import type { WebClient } from '@slack/web-api';
  2. import axios, { type AxiosError } from 'axios';
  3. import { requiredScopes } from '../consts';
  4. import type { ConnectionStatus } from '../interfaces/connection-status';
  5. import { markdownSectionBlock } from './block-kit-builder';
  6. import { generateWebClient } from './webclient-factory';
  7. /**
  8. * Check whether the HTTP server responds or not.
  9. *
  10. * @param serverUri Server URI to connect
  11. * @returns AxiosError when error is occured
  12. */
  13. export const connectToHttpServer = async (
  14. serverUri: string,
  15. ): Promise<undefined | AxiosError> => {
  16. try {
  17. await axios.get(serverUri, { maxRedirects: 0, timeout: 3000 });
  18. } catch (err) {
  19. return err as AxiosError;
  20. }
  21. };
  22. /**
  23. * Check whether the Slack API server responds or not.
  24. *
  25. * @returns AxiosError when error is occured
  26. */
  27. export const connectToSlackApiServer = async (): Promise<
  28. undefined | AxiosError
  29. > => {
  30. return connectToHttpServer('https://slack.com/api/');
  31. };
  32. /**
  33. * Test Slack API
  34. * @param client
  35. */
  36. // biome-ignore lint/suspicious/noExplicitAny: ignore
  37. const testSlackApiServer = async (client: WebClient): Promise<any> => {
  38. const result = await client.api.test();
  39. if (!result.ok) {
  40. throw new Error(result.error);
  41. }
  42. return result;
  43. };
  44. // biome-ignore lint/suspicious/noExplicitAny: ignore
  45. const checkSlackScopes = (resultTestSlackApiServer: any) => {
  46. const slackScopes = resultTestSlackApiServer.response_metadata.scopes;
  47. const isPassedScopeCheck = requiredScopes.every((e) =>
  48. slackScopes.includes(e),
  49. );
  50. if (!isPassedScopeCheck) {
  51. throw new Error(
  52. `The scopes you registered are not appropriate. Required scopes are ${requiredScopes}`,
  53. );
  54. }
  55. };
  56. /**
  57. * Retrieve Slack workspace name
  58. * @param client
  59. */
  60. const retrieveWorkspaceName = async (client: WebClient): Promise<string> => {
  61. const result = await client.team.info();
  62. if (!result.ok) {
  63. throw new Error(result.error);
  64. }
  65. // biome-ignore lint/suspicious/noExplicitAny: ignore
  66. return (result as any).team?.name;
  67. };
  68. /**
  69. * @param token bot OAuth token
  70. * @returns
  71. */
  72. export const getConnectionStatus = async (
  73. token: string,
  74. ): Promise<ConnectionStatus> => {
  75. const client = generateWebClient(token);
  76. const status: ConnectionStatus = {};
  77. try {
  78. // try to connect
  79. const resultTestSlackApiServer = await testSlackApiServer(client);
  80. // check scope
  81. await checkSlackScopes(resultTestSlackApiServer);
  82. // retrieve workspace name
  83. status.workspaceName = await retrieveWorkspaceName(client);
  84. } catch (err) {
  85. status.error = err as Error;
  86. }
  87. return status;
  88. };
  89. /**
  90. * Get token string to ConnectionStatus map
  91. * @param keys Array of bot OAuth token or specific key
  92. * @param botTokenResolver function to convert from key to token
  93. * @returns
  94. */
  95. export const getConnectionStatuses = async (
  96. keys: string[],
  97. botTokenResolver?: (key: string) => string,
  98. ): Promise<{ [key: string]: ConnectionStatus }> => {
  99. const map = keys.reduce<Promise<Map<string, ConnectionStatus>>>(
  100. async (acc, key) => {
  101. let token = key;
  102. if (botTokenResolver != null) {
  103. token = botTokenResolver(key);
  104. }
  105. const status: ConnectionStatus = await getConnectionStatus(token);
  106. (await acc).set(key, status);
  107. return acc;
  108. },
  109. // define initial accumulator
  110. Promise.resolve(new Map<string, ConnectionStatus>()),
  111. );
  112. // convert to object
  113. return Object.fromEntries(await map);
  114. };
  115. export const sendSuccessMessage = async (
  116. token: string,
  117. channel: string,
  118. appSiteUrl: string,
  119. ): Promise<void> => {
  120. const client = generateWebClient(token);
  121. await client.chat.postMessage({
  122. channel,
  123. text: 'Success',
  124. blocks: [
  125. markdownSectionBlock(`:tada: Successfully tested with ${appSiteUrl}.`),
  126. markdownSectionBlock(
  127. 'Now your GROWI and Slack integration is ready to use :+1:',
  128. ),
  129. ],
  130. });
  131. };