6
6
Aes128Key ,
7
7
Aes256Key ,
8
8
aes256RandomKey ,
9
+ aesDecrypt ,
9
10
aesEncrypt ,
10
11
AesKey ,
11
12
decryptKey ,
@@ -14,14 +15,15 @@ import {
14
15
unauthenticatedAesDecrypt ,
15
16
} from "@tutao/tutanota-crypto"
16
17
import { UserFacade } from "../UserFacade.js"
17
- import { EncryptedDbKeyBaseMetaData , EncryptedIndexerMetaData , Metadata , ObjectStoreName } from "../../search/IndexTables.js"
18
+ import { EncryptedDbKeyBaseMetaData , EncryptedIndexerMetaData , LocalDraftDataOS , Metadata , ObjectStoreName } from "../../search/IndexTables.js"
18
19
import { DbError } from "../../../common/error/DbError.js"
19
20
import { checkKeyVersionConstraints , KeyLoaderFacade } from "../KeyLoaderFacade.js"
20
- import type { QueuedBatch } from "../../EventQueue.js"
21
21
import { _encryptKeyWithVersionedKey , VersionedKey } from "../../crypto/CryptoWrapper.js"
22
22
import { EntityUpdateData , isUpdateForTypeRef } from "../../../common/utils/EntityUpdateUtils"
23
+ import * as cborg from "cborg"
24
+ import { customTypeDecoders , customTypeEncoders } from "../../offline/OfflineStorage"
23
25
24
- const VERSION : number = 2
26
+ const VERSION : number = 3
25
27
const DB_KEY_PREFIX : string = "ConfigStorage"
26
28
const ExternalImageListOS : ObjectStoreName = "ExternalAllowListOS"
27
29
export const ConfigurationMetaDataOS : ObjectStoreName = "MetaDataOS"
@@ -43,6 +45,32 @@ export async function decryptLegacyItem(encryptedAddress: Uint8Array, key: Aes25
43
45
return utf8Uint8ArrayToString ( unauthenticatedAesDecrypt ( key , concat ( iv , encryptedAddress ) ) )
44
46
}
45
47
48
+ const LOCAL_DRAFT_VERSION : number = 1
49
+ export type LocalDraftAddress = {
50
+ name : string
51
+ address : string
52
+ }
53
+ export type LocalAutosavedDraftData = {
54
+ version : number
55
+
56
+ saveTime : number
57
+ mailId : IdTuple | null
58
+ mailGroupId : Id
59
+
60
+ subject : string
61
+ body : string
62
+ bodyOnServer : string | null
63
+ confidential : boolean
64
+
65
+ senderAddress : string
66
+ to : LocalDraftAddress [ ]
67
+ cc : LocalDraftAddress [ ]
68
+ bcc : LocalDraftAddress [ ]
69
+ }
70
+
71
+ // We only support one draft maximum, destroying any previous draft if one is currently stored.
72
+ const LOCAL_DRAFT_KEY = "current"
73
+
46
74
/**
47
75
* A local configuration database that can be used as an alternative to DeviceConfig:
48
76
* Ideal for cases where the configuration values should be stored encrypted,
@@ -65,6 +93,78 @@ export class ConfigurationDatabase {
65
93
} )
66
94
}
67
95
96
+ /**
97
+ * Save the draft data to the database, overwriting one if there is one there.
98
+ * @param draftUpdateDataWithoutVersion data to write
99
+ */
100
+ async setAutosavedDraftData ( draftUpdateDataWithoutVersion : Omit < LocalAutosavedDraftData , "version" > ) : Promise < void > {
101
+ const { db, metaData } = await this . db . getAsync ( )
102
+ if ( ! db . indexingSupported ) return
103
+
104
+ const draftUpdateData : LocalAutosavedDraftData = Object . assign ( { } , draftUpdateDataWithoutVersion , { version : LOCAL_DRAFT_VERSION } )
105
+
106
+ try {
107
+ const transaction = await db . createTransaction ( false , [ LocalDraftDataOS ] )
108
+ const encoded = cborg . encode ( draftUpdateData , { typeEncoders : customTypeEncoders } )
109
+ const encryptedData = aesEncrypt ( metaData . key , encoded , metaData . iv )
110
+ await transaction . put ( LocalDraftDataOS , LOCAL_DRAFT_KEY , encryptedData )
111
+ } catch ( e ) {
112
+ if ( e instanceof DbError ) {
113
+ console . error ( "failed to save draft:" , e . message )
114
+ return
115
+ }
116
+ throw e
117
+ }
118
+ }
119
+
120
+ /**
121
+ * @return the locally stored draft data, if any, or null
122
+ */
123
+ async getAutosavedDraftData ( ) : Promise < LocalAutosavedDraftData | null > {
124
+ const { db, metaData } = await this . db . getAsync ( )
125
+ if ( ! db . indexingSupported ) {
126
+ return null
127
+ }
128
+
129
+ try {
130
+ const transaction = await db . createTransaction ( false , [ LocalDraftDataOS ] )
131
+ const data = await transaction . get < Uint8Array > ( LocalDraftDataOS , LOCAL_DRAFT_KEY )
132
+ if ( data == null ) {
133
+ return null
134
+ }
135
+
136
+ const decryptedData = aesDecrypt ( metaData . key , data )
137
+ const decoded = cborg . decode ( decryptedData , { tags : customTypeDecoders } )
138
+
139
+ return decoded as LocalAutosavedDraftData
140
+ } catch ( e ) {
141
+ if ( e instanceof DbError ) {
142
+ console . error ( "failed to load draft:" , e . message )
143
+ return null
144
+ }
145
+ throw e
146
+ }
147
+ }
148
+
149
+ /**
150
+ * Deletes any locally saved draft data, if any
151
+ */
152
+ async clearAutosavedDraftData ( ) : Promise < void > {
153
+ const { db } = await this . db . getAsync ( )
154
+ if ( ! db . indexingSupported ) return
155
+
156
+ try {
157
+ const transaction = await db . createTransaction ( false , [ LocalDraftDataOS ] )
158
+ await transaction . delete ( LocalDraftDataOS , LOCAL_DRAFT_KEY )
159
+ } catch ( e ) {
160
+ if ( e instanceof DbError ) {
161
+ console . error ( "failed to load draft:" , e . message )
162
+ return
163
+ }
164
+ throw e
165
+ }
166
+ }
167
+
68
168
async addExternalImageRule ( address : string , rule : ExternalImageRule ) : Promise < void > {
69
169
const { db, metaData } = await this . db . getAsync ( )
70
170
if ( ! db . indexingSupported ) return
@@ -101,6 +201,7 @@ export class ConfigurationDatabase {
101
201
db . createObjectStore ( ExternalImageListOS , {
102
202
keyPath : "address" ,
103
203
} )
204
+ db . createObjectStore ( LocalDraftDataOS )
104
205
}
105
206
const metaData =
106
207
( await loadEncryptionMetadata ( dbFacade , id , keyLoaderFacade , ConfigurationMetaDataOS ) ) ||
@@ -118,7 +219,12 @@ export class ConfigurationDatabase {
118
219
await deleteTransaction . delete ( ExternalImageListOS , entry . key )
119
220
}
120
221
}
222
+
223
+ if ( event . oldVersion < 3 ) {
224
+ db . createObjectStore ( LocalDraftDataOS )
225
+ }
121
226
} )
227
+
122
228
const metaData =
123
229
( await loadEncryptionMetadata ( db , id , keyLoaderFacade , ConfigurationMetaDataOS ) ) ||
124
230
( await initializeDb ( db , id , keyLoaderFacade , ConfigurationMetaDataOS ) )
0 commit comments