LinkSharedService.ts 3.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127
  1. import { type GrowiEventProcessor, REQUEST_TIMEOUT_FOR_PTOG } from '@growi/slack';
  2. import type { WebClient } from '@slack/web-api';
  3. import { Inject, Service } from '@tsed/di';
  4. import axios from 'axios';
  5. import { RelationRepository } from '~/repositories/relation';
  6. import loggerFactory from '~/utils/logger';
  7. const logger = loggerFactory('slackbot-proxy:services:LinkSharedService');
  8. type LinkSharedEventLink = {
  9. url: string,
  10. domain: string,
  11. }
  12. // aliases
  13. type GrowiOrigin = string;
  14. type TokenPtoG = string;
  15. export type LinkSharedRequestEvent = {
  16. channel: string,
  17. // eslint-disable-next-line camelcase
  18. message_ts: string,
  19. links: LinkSharedEventLink[],
  20. }
  21. type PrivateData = {
  22. isPublic: false,
  23. path: string,
  24. }
  25. type PublicData = {
  26. isPublic: true,
  27. path: string,
  28. pageBody: string,
  29. updatedAt: string,
  30. commentCount: number,
  31. }
  32. export type DataForLinkShared = PrivateData | PublicData;
  33. @Service()
  34. export class LinkSharedService implements GrowiEventProcessor {
  35. @Inject()
  36. relationRepository: RelationRepository;
  37. shouldHandleEvent(eventType: string): boolean {
  38. return eventType === 'link_shared';
  39. }
  40. async processEvent(client: WebClient, event: LinkSharedRequestEvent): Promise<void> {
  41. const { links } = event;
  42. const origins: string[] = links.map((link: LinkSharedEventLink) => (new URL(link.url)).origin);
  43. const originToTokenPtoGMap: Map<GrowiOrigin, TokenPtoG> = await this.generateOriginToTokenPtoGMapFromOrigins(origins); // get tokenPtoG at once
  44. // forward to GROWI
  45. const result = await this.forwardToEachGrowiOrigin(origins, event, originToTokenPtoGMap);
  46. // log error
  47. this.logErrorRejectedResults(result);
  48. }
  49. // generate Map<GrowiOrigin, TokenPtoG>
  50. async generateOriginToTokenPtoGMapFromOrigins(origins: GrowiOrigin[]): Promise<Map<GrowiOrigin, TokenPtoG>> {
  51. const originToTokenPtoGMap: Map<GrowiOrigin, TokenPtoG> = new Map();
  52. // get relations using origins at once
  53. const relations = await this.relationRepository.findAllByGrowiUris(origins);
  54. // increment map using relation.growiUri & relation.tokenPtoG
  55. relations.forEach((relation) => {
  56. originToTokenPtoGMap.set(relation.growiUri, relation.tokenPtoG);
  57. });
  58. return originToTokenPtoGMap;
  59. }
  60. async forwardToEachGrowiOrigin(
  61. origins: string[], event: LinkSharedRequestEvent, originToTokenPtoGMap: Map<GrowiOrigin, TokenPtoG>,
  62. ): Promise<PromiseSettledResult<void>[]> {
  63. return Promise.allSettled(origins.map(async(origin) => {
  64. const requestBody = {
  65. growiBotEvent: {
  66. eventType: 'link_shared',
  67. event,
  68. },
  69. data: {
  70. origin,
  71. },
  72. };
  73. try {
  74. // ensure tokenPtoG exists
  75. const tokenPtoG = originToTokenPtoGMap.get(origin);
  76. if (tokenPtoG == null) throw new Error('tokenPtoG is null');
  77. const url = new URL('/_api/v3/slack-integration/proxied/events', origin);
  78. await axios.post(url.toString(),
  79. requestBody,
  80. {
  81. headers: {
  82. 'x-growi-ptog-tokens': tokenPtoG,
  83. },
  84. timeout: REQUEST_TIMEOUT_FOR_PTOG,
  85. });
  86. }
  87. catch (err) {
  88. logger.error(`Error occurred while request to growi (origin=${origin}):`, err);
  89. throw err;
  90. }
  91. }));
  92. }
  93. // Promise util method to output rejected results
  94. private logErrorRejectedResults<T>(results: PromiseSettledResult<T>[]): void {
  95. const rejectedResults: PromiseRejectedResult[] = results.filter((result): result is PromiseRejectedResult => result.status === 'rejected');
  96. rejectedResults.forEach((rejected, i) => {
  97. logger.error(`Error occurred (count: ${i}): `, rejected.reason.toString());
  98. });
  99. }
  100. }