1 import { getCanWrite } from '@proton/shared/lib/drive/permissions'
2 import { Result } from '@proton/docs-shared'
3 import { DocumentRole, type DocumentMetaInterface } from '@proton/docs-shared'
4 import type { NodeMeta, PublicNodeMeta, DecryptedNode } from '@proton/drive-store'
5 import type { GetDocumentMeta } from './GetDocumentMeta'
6 import { getErrorString } from '../Util/GetErrorString'
7 import type { DocumentEntitlements, PublicDocumentEntitlements } from '../Types/DocumentEntitlements'
8 import { rawPermissionToRole } from '../Types/DocumentEntitlements'
9 import type { GetNode } from './GetNode'
10 import type { DriveCompatWrapper } from '@proton/drive-store/lib/DriveCompatWrapper'
11 import type { LoadCommit } from './LoadCommit'
12 import type { LoggerInterface } from '@proton/utils/logs'
13 import type { DecryptedCommit } from '../Models/DecryptedCommit'
15 type LoadDocumentResult<E extends DocumentEntitlements | PublicDocumentEntitlements> = {
17 meta: DocumentMetaInterface
19 decryptedCommit?: DecryptedCommit
23 * Performs initial loading procedure for document, including fetching keys and latest commit binary from DX.
25 export class LoadDocument {
27 private compatWrapper: DriveCompatWrapper,
28 private getDocumentMeta: GetDocumentMeta,
29 private getNode: GetNode,
30 private loadCommit: LoadCommit,
31 private logger: LoggerInterface,
34 async executePrivate(nodeMeta: NodeMeta): Promise<Result<LoadDocumentResult<DocumentEntitlements>>> {
35 if (!this.compatWrapper.userCompat) {
36 return Result.fail('User drive compat not found')
39 const [nodeResult, keysResult, fetchResult, permissionsResult] = await Promise.all([
40 this.getNode.execute(nodeMeta).catch((error) => {
41 throw new Error(`Failed to load node: ${error}`)
43 this.compatWrapper.userCompat.getDocumentKeys(nodeMeta).catch((error) => {
44 throw new Error(`Failed to load keys: ${error}`)
46 this.getDocumentMeta.execute(nodeMeta).catch((error) => {
47 throw new Error(`Failed to fetch document metadata: ${error}`)
49 this.compatWrapper.userCompat.getNodePermissions(nodeMeta).catch((error) => {
50 throw new Error(`Failed to load permissions: ${error}`)
54 if (fetchResult.isFailed()) {
55 return Result.fail(fetchResult.getError())
58 if (nodeResult.isFailed()) {
59 return Result.fail(nodeResult.getError())
61 const node = nodeResult.getValue().node
63 const serverBasedMeta: DocumentMetaInterface = fetchResult.getValue()
64 if (!serverBasedMeta) {
65 return Result.fail('Document meta not found')
68 const decryptedMeta = serverBasedMeta.copyWithNewValues({ name: node.name })
70 if (!permissionsResult) {
71 return Result.fail('Unable to load permissions')
75 return Result.fail('Unable to load all necessary data')
78 const entitlements: DocumentEntitlements = {
80 role: permissionsResult ? rawPermissionToRole(permissionsResult) : new DocumentRole('PublicViewer'),
84 const latestCommitId = serverBasedMeta.latestCommitId()
85 let decryptedCommit: DecryptedCommit | undefined
88 const decryptResult = await this.loadCommit.execute(
91 entitlements.keys.documentContentKey,
93 if (decryptResult.isFailed()) {
94 return Result.fail(decryptResult.getError())
97 decryptedCommit = decryptResult.getValue()
98 this.logger.info(`Downloaded and decrypted commit with ${decryptedCommit?.numberOfUpdates()} updates`)
101 return Result.ok({ entitlements, meta: decryptedMeta, node: node, decryptedCommit })
103 return Result.fail(getErrorString(error) ?? 'Failed to load document')
108 nodeMeta: PublicNodeMeta,
109 publicEditingEnabled: boolean,
110 ): Promise<Result<LoadDocumentResult<PublicDocumentEntitlements>>> {
111 if (!this.compatWrapper.publicCompat) {
112 return Result.fail('Public drive compat not found')
115 const permissions = this.compatWrapper.publicCompat.permissions
118 return Result.fail('Permissions not yet loaded')
122 const [nodeResult, keysResult, fetchResult] = await Promise.all([
123 this.getNode.execute(nodeMeta).catch((error) => {
124 throw new Error(`Failed to load public node: ${error}`)
126 this.compatWrapper.publicCompat.getDocumentKeys(nodeMeta).catch((error) => {
127 throw new Error(`Failed to load public keys: ${error}`)
129 this.getDocumentMeta.execute(nodeMeta).catch((error) => {
130 throw new Error(`Failed to fetch document metadata: ${error}`)
134 const decryptedNode = nodeResult.getValue().node
136 if (fetchResult.isFailed()) {
137 return Result.fail(fetchResult.getError())
140 const serverBasedMeta: DocumentMetaInterface = fetchResult.getValue()
141 if (!serverBasedMeta) {
142 return Result.fail('Document meta not found')
145 const decryptedMeta = serverBasedMeta.copyWithNewValues({ name: decryptedNode.name })
148 return Result.fail('Unable to load all necessary data')
152 * We attempt to determine if the current public session user can load the actual document meta via the
155 * If it succeeds, this means the user has some sort of access to this document, and can perform
156 * actions like duplicating it.
158 const authenticatedMetaAttempt = await this.getDocumentMeta.execute({
159 volumeId: decryptedMeta.volumeId,
160 linkId: nodeMeta.linkId,
163 const doesHaveAccessToDoc = !authenticatedMetaAttempt.isFailed()
165 const role = (() => {
166 if (publicEditingEnabled && getCanWrite(permissions)) {
167 return new DocumentRole('PublicEditor')
169 if (doesHaveAccessToDoc) {
170 return new DocumentRole('PublicViewerWithAccess')
172 return new DocumentRole('PublicViewer')
175 const entitlements: PublicDocumentEntitlements = {
181 const latestCommitId = serverBasedMeta.latestCommitId()
182 let decryptedCommit: DecryptedCommit | undefined
184 if (latestCommitId) {
185 const decryptResult = await this.loadCommit.execute(
188 entitlements.keys.documentContentKey,
190 if (decryptResult.isFailed()) {
191 return Result.fail(decryptResult.getError())
194 decryptedCommit = decryptResult.getValue()
195 this.logger.info(`Downloaded and decrypted commit with ${decryptedCommit?.numberOfUpdates()} updates`)
198 return Result.ok({ entitlements, meta: decryptedMeta, node: decryptedNode, decryptedCommit })
200 return Result.fail(getErrorString(error) ?? 'Failed to load document')