DRVDOC-1255: Fix issue with recent docs timestamp
[ProtonMail-WebClient.git] / packages / docs-core / lib / Services / RecentDocuments / RecentDocumentsService.ts
blobbc3a7bab2003bc2bfcf63a02b418e6ea8a2e2dfc
1 import { InternalEventPublishStrategy, ServerTime, type InternalEventBus } from '@proton/docs-shared'
2 import type { DecryptedNode, DriveCompat } from '@proton/drive-store'
4 import type {
5   EventRecentDocumentStateUpdated,
6   RecentDocument,
7   RecentDocumentServiceState,
8   RecentDocumentsInterface,
9   RecentDocumentsSnapshot,
10   RecentDocumentsSnapshotData,
11 } from './types'
12 import { RecentDocumentStateUpdatedEvent } from './types'
13 import type { DocsApi } from '../../Api/DocsApi'
14 import type { LoggerInterface } from '@proton/utils/logs'
16 type ResolvedItem = {
17   node: DecryptedNode
18   nodePath: string[]
19   isOwnedByOthers: boolean
22 export class RecentDocumentsService implements RecentDocumentsInterface {
23   state: RecentDocumentServiceState = 'not_fetched'
24   recentDocuments: RecentDocument[] = []
25   record: Map<string, ResolvedItem> = new Map()
27   constructor(
28     private eventBus: InternalEventBus,
29     private driveCompat: DriveCompat,
30     private docsApi: DocsApi,
31     private logger: LoggerInterface,
32   ) {}
34   async notifyStateUpdated() {
35     const event: EventRecentDocumentStateUpdated = {
36       type: RecentDocumentStateUpdatedEvent,
37       payload: undefined,
38     }
40     await this.eventBus.publishSync(event, InternalEventPublishStrategy.SEQUENCE)
41   }
43   async setStatusAndNotify(status: RecentDocumentServiceState) {
44     this.state = status
45     await this.notifyStateUpdated()
46   }
48   async fetch() {
49     await this.setStatusAndNotify('fetching')
51     const response = await this.docsApi.fetchRecentDocuments()
52     if (response.isFailed()) {
53       await this.setStatusAndNotify('not_fetched')
54       return
55     }
57     this.recentDocuments = response.getValue().RecentDocuments.map((item) => ({
58       linkId: item.LinkID,
59       shareId: item.ContextShareID,
60       lastViewed: new ServerTime(item.LastOpenTime),
61     }))
63     await this.setStatusAndNotify('resolving')
65     const ids = this.recentDocuments.map((recentDocument) => ({
66       linkId: recentDocument.linkId,
67       shareId: recentDocument.shareId,
68     }))
70     await Promise.all(ids.map((item) => this.resolveItem(item)))
72     await this.setStatusAndNotify('done')
73   }
75   async resolveItem(item: { linkId: string; shareId: string }) {
76     try {
77       const [node, nodePath, isNodeAtIndexOwnedByOthers] = await Promise.all([
78         this.driveCompat.getNodes([item]).then((nodes) => nodes[0]),
79         this.driveCompat.getNodePaths([item]).then((paths) => paths[0]),
80         this.driveCompat.getNodesAreShared([item]).then((shared) => shared[0]),
81       ])
83       const record: ResolvedItem = {
84         node,
85         nodePath: nodePath.map((pathItem) => pathItem.name),
86         isOwnedByOthers: isNodeAtIndexOwnedByOthers,
87       }
89       this.record.set(item.linkId, record)
91       await this.notifyStateUpdated()
92     } catch (error) {
93       this.logger.error('Failed to resolve recent document', { error, item })
94     }
95   }
97   async trashDocument(recentDocument: RecentDocumentsSnapshotData): Promise<void> {
98     await this.setStatusAndNotify('fetching')
100     if (!recentDocument.parentLinkId) {
101       throw new Error('Node does not have parent link ID')
102     }
104     await this.driveCompat.trashDocument(
105       { linkId: recentDocument.linkId, volumeId: recentDocument.volumeId },
106       recentDocument.parentLinkId,
107     )
109     await this.setStatusAndNotify('done')
110   }
112   getSnapshot(): RecentDocumentsSnapshot {
113     const data: RecentDocumentsSnapshot['data'] = this.recentDocuments
114       .map((recentDocument) => {
115         const record = this.record.get(recentDocument.linkId)
116         if (!record) {
117           return undefined
118         }
120         return {
121           name: record.node.name,
122           linkId: recentDocument.linkId,
123           volumeId: record.node.volumeId,
124           parentLinkId: record.node.parentNodeId,
125           location: record.isOwnedByOthers ? ['Shared with me'] : record.nodePath,
126           isSharedWithMe: record.isOwnedByOthers,
127           lastViewed: recentDocument.lastViewed,
128           createdBy: record.node.signatureAddress,
129         }
130       })
131       .filter((item) => !!item)
133     return {
134       data,
135       state: this.state,
136     }
137   }