SyncingServer.ts 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137
  1. import * as grpc from '@grpc/grpc-js'
  2. import { Status } from '@grpc/grpc-js/build/src/constants'
  3. import { ISyncingServer, SyncRequest, SyncResponse } from '@standardnotes/grpc'
  4. import { Logger } from 'winston'
  5. import { MapperInterface } from '@standardnotes/domain-core'
  6. import { ItemHash } from '../../Domain/Item/ItemHash'
  7. import { SyncItems } from '../../Domain/UseCase/Syncing/SyncItems/SyncItems'
  8. import { ApiVersion } from '../../Domain/Api/ApiVersion'
  9. import { SyncResponseFactoryResolverInterface } from '../../Domain/Item/SyncResponse/SyncResponseFactoryResolverInterface'
  10. import { SyncResponse20200115 } from '../../Domain/Item/SyncResponse/SyncResponse20200115'
  11. export class SyncingServer implements ISyncingServer {
  12. constructor(
  13. private syncItemsUseCase: SyncItems,
  14. private syncResponseFactoryResolver: SyncResponseFactoryResolverInterface,
  15. private mapper: MapperInterface<SyncResponse20200115, SyncResponse>,
  16. private logger: Logger,
  17. ) {}
  18. async syncItems(
  19. call: grpc.ServerUnaryCall<SyncRequest, SyncResponse>,
  20. callback: grpc.sendUnaryData<SyncResponse>,
  21. ): Promise<void> {
  22. try {
  23. this.logger.debug('[SyncingServer] Syncing items via gRPC')
  24. const itemHashesRPC = call.request.getItemsList()
  25. const itemHashes: ItemHash[] = []
  26. for (const itemHash of itemHashesRPC) {
  27. const itemHashOrError = ItemHash.create({
  28. uuid: itemHash.getUuid(),
  29. content: itemHash.hasContent() ? itemHash.getContent() : undefined,
  30. content_type: itemHash.hasContentType() ? (itemHash.getContentType() as string) : null,
  31. deleted: itemHash.hasDeleted() ? itemHash.getDeleted() : undefined,
  32. duplicate_of: itemHash.hasDuplicateOf() ? itemHash.getDuplicateOf() : undefined,
  33. auth_hash: itemHash.hasAuthHash() ? itemHash.getAuthHash() : undefined,
  34. enc_item_key: itemHash.hasEncItemKey() ? itemHash.getEncItemKey() : undefined,
  35. items_key_id: itemHash.hasItemsKeyId() ? itemHash.getItemsKeyId() : undefined,
  36. created_at: itemHash.hasCreatedAt() ? itemHash.getCreatedAt() : undefined,
  37. created_at_timestamp: itemHash.hasCreatedAtTimestamp() ? itemHash.getCreatedAtTimestamp() : undefined,
  38. updated_at: itemHash.hasUpdatedAt() ? itemHash.getUpdatedAt() : undefined,
  39. updated_at_timestamp: itemHash.hasUpdatedAtTimestamp() ? itemHash.getUpdatedAtTimestamp() : undefined,
  40. user_uuid: call.metadata.get('userUuid').pop() as string,
  41. key_system_identifier: itemHash.hasKeySystemIdentifier()
  42. ? (itemHash.getKeySystemIdentifier() as string)
  43. : null,
  44. shared_vault_uuid: itemHash.hasSharedVaultUuid() ? (itemHash.getSharedVaultUuid() as string) : null,
  45. })
  46. if (itemHashOrError.isFailed()) {
  47. const metadata = new grpc.Metadata()
  48. metadata.set('x-sync-error-message', itemHashOrError.getError())
  49. metadata.set('x-sync-error-response-code', '400')
  50. return callback(
  51. {
  52. code: Status.INVALID_ARGUMENT,
  53. message: itemHashOrError.getError(),
  54. name: 'INVALID_ARGUMENT',
  55. metadata,
  56. },
  57. null,
  58. )
  59. }
  60. itemHashes.push(itemHashOrError.getValue())
  61. }
  62. let sharedVaultUuids: string[] | undefined = undefined
  63. const sharedVaultUuidsList = call.request.getSharedVaultUuidsList()
  64. if (sharedVaultUuidsList.length > 0) {
  65. sharedVaultUuids = sharedVaultUuidsList
  66. }
  67. const apiVersion = call.request.hasApiVersion() ? (call.request.getApiVersion() as string) : ApiVersion.v20161215
  68. const userUuid = call.metadata.get('x-user-uuid').pop() as string
  69. const readOnlyAccess = call.metadata.get('x-read-only-access').pop() === 'true'
  70. if (readOnlyAccess) {
  71. this.logger.info('Syncing with read-only access', {
  72. codeTag: 'SyncingServer',
  73. userId: userUuid,
  74. })
  75. }
  76. const syncResult = await this.syncItemsUseCase.execute({
  77. userUuid,
  78. itemHashes,
  79. computeIntegrityHash: call.request.hasComputeIntegrity() ? call.request.getComputeIntegrity() === true : false,
  80. syncToken: call.request.hasSyncToken() ? call.request.getSyncToken() : undefined,
  81. cursorToken: call.request.getCursorToken() ? call.request.getCursorToken() : undefined,
  82. limit: call.request.hasLimit() ? call.request.getLimit() : undefined,
  83. contentType: call.request.hasContentType() ? call.request.getContentType() : undefined,
  84. apiVersion,
  85. snjsVersion: call.metadata.get('x-snjs-version').pop() as string,
  86. readOnlyAccess,
  87. sessionUuid: call.metadata.get('x-session-uuid').pop() as string,
  88. sharedVaultUuids,
  89. isFreeUser: call.metadata.get('x-is-free-user').pop() === 'true',
  90. })
  91. if (syncResult.isFailed()) {
  92. const metadata = new grpc.Metadata()
  93. metadata.set('x-sync-error-message', syncResult.getError())
  94. metadata.set('x-sync-error-response-code', '400')
  95. return callback(
  96. {
  97. code: Status.INVALID_ARGUMENT,
  98. message: syncResult.getError(),
  99. name: 'INVALID_ARGUMENT',
  100. metadata,
  101. },
  102. null,
  103. )
  104. }
  105. const syncResponse = await this.syncResponseFactoryResolver
  106. .resolveSyncResponseFactoryVersion(apiVersion)
  107. .createResponse(syncResult.getValue())
  108. const projection = this.mapper.toProjection(syncResponse as SyncResponse20200115)
  109. callback(null, projection)
  110. } catch (error) {
  111. this.logger.error(`[SyncingServer] Error syncing items via gRPC: ${(error as Error).message}`)
  112. return callback(
  113. {
  114. code: Status.UNKNOWN,
  115. message: 'An error occurred while syncing items',
  116. name: 'UNKNOWN',
  117. },
  118. null,
  119. )
  120. }
  121. }
  122. }