ExtensionsHttpService.ts 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177
  1. import { KeyParamsData } from '@standardnotes/responses'
  2. import { DomainEventInterface, DomainEventPublisherInterface } from '@standardnotes/domain-events'
  3. import { EmailLevel } from '@standardnotes/domain-core'
  4. import { AxiosInstance } from 'axios'
  5. import { Logger } from 'winston'
  6. import { DomainEventFactoryInterface } from '../Event/DomainEventFactoryInterface'
  7. import { ContentDecoderInterface } from '../Item/ContentDecoderInterface'
  8. import { ItemRepositoryInterface } from '../Item/ItemRepositoryInterface'
  9. import { ExtensionName } from './ExtensionName'
  10. import { ExtensionsHttpServiceInterface } from './ExtensionsHttpServiceInterface'
  11. import { SendItemsToExtensionsServerDTO } from './SendItemsToExtensionsServerDTO'
  12. import { getBody as googleDriveBody, getSubject as googleDriveSubject } from '../Email/GoogleDriveBackupFailed'
  13. import { getBody as dropboxBody, getSubject as dropboxSubject } from '../Email/DropboxBackupFailed'
  14. import { getBody as oneDriveBody, getSubject as oneDriveSubject } from '../Email/OneDriveBackupFailed'
  15. export class ExtensionsHttpService implements ExtensionsHttpServiceInterface {
  16. constructor(
  17. private httpClient: AxiosInstance,
  18. private itemRepository: ItemRepositoryInterface,
  19. private contentDecoder: ContentDecoderInterface,
  20. private domainEventPublisher: DomainEventPublisherInterface,
  21. private domainEventFactory: DomainEventFactoryInterface,
  22. private logger: Logger,
  23. ) {}
  24. async triggerCloudBackupOnExtensionsServer(dto: {
  25. cloudProvider: 'DROPBOX' | 'GOOGLE_DRIVE' | 'ONE_DRIVE'
  26. extensionsServerUrl: string
  27. backupFilename: string
  28. authParams: KeyParamsData
  29. forceMute: boolean
  30. userUuid: string
  31. }): Promise<void> {
  32. let sent = false
  33. try {
  34. const payload: Record<string, unknown> = {
  35. backup_filename: dto.backupFilename,
  36. auth_params: dto.authParams,
  37. silent: dto.forceMute,
  38. user_uuid: dto.userUuid,
  39. }
  40. const response = await this.httpClient.request({
  41. method: 'POST',
  42. url: dto.extensionsServerUrl,
  43. headers: {
  44. 'Content-Type': 'application/json',
  45. },
  46. data: payload,
  47. validateStatus:
  48. /* istanbul ignore next */
  49. (status: number) => status >= 200 && status < 500,
  50. })
  51. sent = response.status >= 200 && response.status < 300
  52. } catch (error) {
  53. this.logger.error(`[${dto.userUuid}] Failed to send a request to extensions server: ${(error as Error).message}`)
  54. }
  55. if (!sent && !dto.forceMute) {
  56. await this.domainEventPublisher.publish(
  57. this.createCloudBackupFailedEventBasedOnProvider(dto.cloudProvider, dto.authParams.identifier as string),
  58. )
  59. }
  60. }
  61. async sendItemsToExtensionsServer(dto: SendItemsToExtensionsServerDTO): Promise<void> {
  62. let sent = false
  63. try {
  64. const payload: Record<string, unknown> = {
  65. backup_filename: dto.backupFilename,
  66. auth_params: dto.authParams,
  67. silent: dto.forceMute,
  68. user_uuid: dto.userUuid,
  69. }
  70. if (dto.items !== undefined) {
  71. payload.items = dto.items
  72. }
  73. const response = await this.httpClient.request({
  74. method: 'POST',
  75. url: dto.extensionsServerUrl,
  76. headers: {
  77. 'Content-Type': 'application/json',
  78. },
  79. data: payload,
  80. validateStatus:
  81. /* istanbul ignore next */
  82. (status: number) => status >= 200 && status < 500,
  83. })
  84. sent = response.status >= 200 && response.status < 300
  85. } catch (error) {
  86. this.logger.error(`[${dto.userUuid}] Failed to send a request to extensions server: ${(error as Error).message}`)
  87. }
  88. if (!sent && !dto.forceMute) {
  89. await this.domainEventPublisher.publish(
  90. await this.getBackupFailedEvent(dto.extensionId, dto.userUuid, dto.authParams.identifier as string),
  91. )
  92. }
  93. }
  94. private createCloudBackupFailedEventBasedOnProvider(
  95. cloudProvider: 'DROPBOX' | 'GOOGLE_DRIVE' | 'ONE_DRIVE',
  96. email: string,
  97. ): DomainEventInterface {
  98. switch (cloudProvider) {
  99. case 'DROPBOX':
  100. return this.domainEventFactory.createEmailRequestedEvent({
  101. userEmail: email,
  102. level: EmailLevel.LEVELS.FailedCloudBackup,
  103. body: dropboxBody(),
  104. messageIdentifier: 'FAILED_DROPBOX_BACKUP',
  105. subject: dropboxSubject(),
  106. })
  107. case 'GOOGLE_DRIVE':
  108. return this.domainEventFactory.createEmailRequestedEvent({
  109. userEmail: email,
  110. level: EmailLevel.LEVELS.FailedCloudBackup,
  111. body: googleDriveBody(),
  112. messageIdentifier: 'FAILED_GOOGLE_DRIVE_BACKUP',
  113. subject: googleDriveSubject(),
  114. })
  115. case 'ONE_DRIVE':
  116. return this.domainEventFactory.createEmailRequestedEvent({
  117. userEmail: email,
  118. level: EmailLevel.LEVELS.FailedCloudBackup,
  119. body: oneDriveBody(),
  120. messageIdentifier: 'FAILED_ONE_DRIVE_BACKUP',
  121. subject: oneDriveSubject(),
  122. })
  123. }
  124. }
  125. private async getBackupFailedEvent(
  126. extensionId: string,
  127. userUuid: string,
  128. email: string,
  129. ): Promise<DomainEventInterface> {
  130. const extension = await this.itemRepository.findByUuidAndUserUuid(extensionId, userUuid, true)
  131. if (extension === null || !extension.props.content) {
  132. throw Error(`Could not find extensions with id ${extensionId}`)
  133. }
  134. const content = this.contentDecoder.decode(extension.props.content)
  135. switch (this.getExtensionName(content)) {
  136. case ExtensionName.Dropbox:
  137. return this.createCloudBackupFailedEventBasedOnProvider('DROPBOX', email)
  138. case ExtensionName.GoogleDrive:
  139. return this.createCloudBackupFailedEventBasedOnProvider('GOOGLE_DRIVE', email)
  140. case ExtensionName.OneDrive:
  141. return this.createCloudBackupFailedEventBasedOnProvider('ONE_DRIVE', email)
  142. }
  143. }
  144. private getExtensionName(content: Record<string, unknown>): ExtensionName {
  145. if ('name' in content) {
  146. return <ExtensionName>content.name
  147. }
  148. const url = 'url' in content ? <string>content.url : undefined
  149. if (url) {
  150. if (url.indexOf('dbt') !== -1) {
  151. return ExtensionName.Dropbox
  152. } else if (url.indexOf('gdrive') !== -1) {
  153. return ExtensionName.GoogleDrive
  154. } else if (url.indexOf('onedrive') !== -1) {
  155. return ExtensionName.OneDrive
  156. }
  157. }
  158. throw Error('Could not deduce extension name from extension content')
  159. }
  160. }