@secrecy/lib 1.29.0 → 1.30.0-feat-rename-file.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/lib/cache.js +2 -2
- package/dist/lib/client/SecrecyAppClient.js +2 -2
- package/dist/lib/client/SecrecyCloudClient.js +66 -66
- package/dist/lib/client/SecrecyMailClient.js +32 -34
- package/dist/lib/client/convert/data.js +29 -0
- package/dist/lib/client/convert/mail.js +4 -4
- package/dist/lib/client/convert/node.js +7 -7
- package/dist/lib/client/index.js +3 -3
- package/dist/types/cache.d.ts +3 -3
- package/dist/types/client/SecrecyCloudClient.d.ts +10 -10
- package/dist/types/client/convert/data.d.ts +4 -0
- package/dist/types/client/index.d.ts +1 -1
- package/dist/types/client/types/data.d.ts +6 -0
- package/dist/types/client/types/index.d.ts +1 -1
- package/dist/types/client/types/mail.d.ts +5 -5
- package/dist/types/client/types/node.d.ts +4 -4
- package/dist/types/client.d.ts +4 -4
- package/dist/types/index.d.ts +2 -2
- package/dist/types/worker/sodium.d.ts +1 -1
- package/package.json +2 -2
- package/dist/lib/client/convert/file.js +0 -29
- package/dist/types/client/convert/file.d.ts +0 -4
- package/dist/types/client/types/file.d.ts +0 -6
- /package/dist/lib/client/types/{file.js → data.js} +0 -0
- /package/dist/lib/crypto/{file.js → data.js} +0 -0
- /package/dist/types/crypto/{file.d.ts → data.d.ts} +0 -0
package/dist/lib/cache.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { LRUCache } from 'lru-cache';
|
|
2
2
|
import { gigaToBytes } from './utils.js';
|
|
3
|
-
export const
|
|
3
|
+
export const dataCache = new Map();
|
|
4
4
|
export const nodesCache = new Map();
|
|
5
5
|
export const usersCache = new Map();
|
|
6
6
|
export const publicKeysCache = new Map();
|
|
7
|
-
export const
|
|
7
|
+
export const lruCache = new LRUCache({
|
|
8
8
|
max: 500,
|
|
9
9
|
maxSize: gigaToBytes(0.5),
|
|
10
10
|
sizeCalculation: (value) => {
|
|
@@ -82,8 +82,8 @@ export class SecrecyAppClient {
|
|
|
82
82
|
async updateSettings(settings) {
|
|
83
83
|
const updateAppSettings = await this.#apiClient.application.updateSettings.mutate({
|
|
84
84
|
cloudNodeDaysForDelete: settings.cloudNodeDaysForDelete,
|
|
85
|
-
|
|
86
|
-
|
|
85
|
+
historyDataDaysForDelete: settings.historyDataDaysForDelete,
|
|
86
|
+
historyDataMaxCount: settings.historyDataMaxCount,
|
|
87
87
|
});
|
|
88
88
|
return updateAppSettings;
|
|
89
89
|
}
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
import axios from 'axios';
|
|
2
2
|
import ky from 'ky';
|
|
3
3
|
import { encryptName } from '../index.js';
|
|
4
|
-
import { nodesCache,
|
|
5
|
-
import { secretStreamKeygen } from '../crypto/
|
|
4
|
+
import { nodesCache, dataCache, lruCache } from '../cache.js';
|
|
5
|
+
import { secretStreamKeygen } from '../crypto/data.js';
|
|
6
6
|
import { decryptCryptoBox, encryptCryptoBox } from '../crypto/index.js';
|
|
7
7
|
import { compress, decompress } from '../minify/index.js';
|
|
8
8
|
import { sodium } from '../sodium.js';
|
|
9
9
|
import { enumerate, chunks, concatenate } from '../utils/array.js';
|
|
10
10
|
import { md5 } from '../worker/md5.js';
|
|
11
11
|
import { decrypt, encrypt } from '../worker/sodium.js';
|
|
12
|
-
import {
|
|
12
|
+
import { apiDataToExternal } from './convert/data.js';
|
|
13
13
|
import { apiNodeFullToInternalFull, apiNodeToExternal, apiNodeToExternalNodeFull, internalNodeFullToNodeFull, } from './convert/node.js';
|
|
14
14
|
import { promiseAllLimit } from '../utils/promise.js';
|
|
15
15
|
// import { md5 } from "../worker/index.js";
|
|
@@ -46,28 +46,28 @@ export class SecrecyCloudClient {
|
|
|
46
46
|
}
|
|
47
47
|
return internalNodeFullToNodeFull(node);
|
|
48
48
|
}
|
|
49
|
-
async uploadData({
|
|
50
|
-
const
|
|
51
|
-
const
|
|
52
|
-
const compressed = compress(
|
|
53
|
-
const { data:
|
|
54
|
-
const
|
|
49
|
+
async uploadData({ data, encryptProgress, uploadProgress, signal, }) {
|
|
50
|
+
const dataKey = secretStreamKeygen();
|
|
51
|
+
const dataBuffer = data instanceof File ? new Uint8Array(await data.arrayBuffer()) : data;
|
|
52
|
+
const compressed = compress(dataBuffer);
|
|
53
|
+
const { data: encryptedData, md5: md5Data, md5Encrypted, } = await encrypt(dataKey, compressed, encryptProgress, signal);
|
|
54
|
+
const encryptedDataKey = encryptCryptoBox(dataKey, this.#keys.publicKey, this.#keys.privateKey);
|
|
55
55
|
const uploadData = await this.#apiClient.cloud.uploadData.mutate({
|
|
56
|
-
dataSize: BigInt(
|
|
57
|
-
dataSizeBefore: BigInt(
|
|
58
|
-
dataKey: sodium.to_hex(
|
|
56
|
+
dataSize: BigInt(encryptedData.byteLength),
|
|
57
|
+
dataSizeBefore: BigInt(dataBuffer.byteLength),
|
|
58
|
+
dataKey: sodium.to_hex(encryptedDataKey),
|
|
59
59
|
md5Encrypted,
|
|
60
|
-
md5:
|
|
60
|
+
md5: md5Data,
|
|
61
61
|
});
|
|
62
62
|
await uploadProgress?.({
|
|
63
|
-
total:
|
|
63
|
+
total: encryptedData.byteLength,
|
|
64
64
|
current: 0,
|
|
65
65
|
percent: 0,
|
|
66
66
|
});
|
|
67
67
|
if (uploadData.parts.length === 0) {
|
|
68
68
|
await uploadProgress?.({
|
|
69
|
-
total:
|
|
70
|
-
current:
|
|
69
|
+
total: encryptedData.byteLength,
|
|
70
|
+
current: encryptedData.byteLength,
|
|
71
71
|
percent: 1,
|
|
72
72
|
});
|
|
73
73
|
return uploadData.dataId;
|
|
@@ -87,7 +87,7 @@ export class SecrecyCloudClient {
|
|
|
87
87
|
return isUploadEnded;
|
|
88
88
|
};
|
|
89
89
|
const chunkParts = new Array();
|
|
90
|
-
for (const [index, chunk] of enumerate(chunks(
|
|
90
|
+
for (const [index, chunk] of enumerate(chunks(encryptedData, Number(uploadData.dataPartSize)))) {
|
|
91
91
|
chunkParts.push({
|
|
92
92
|
order: index + 1,
|
|
93
93
|
data: chunk,
|
|
@@ -99,8 +99,8 @@ export class SecrecyCloudClient {
|
|
|
99
99
|
progressParts[part] = progressEvent;
|
|
100
100
|
const current = Object.values(progressParts).reduce((prv, cur) => prv + cur.loaded, 0);
|
|
101
101
|
void uploadProgress?.({
|
|
102
|
-
percent: current /
|
|
103
|
-
total:
|
|
102
|
+
percent: current / encryptedData.byteLength,
|
|
103
|
+
total: encryptedData.byteLength,
|
|
104
104
|
current,
|
|
105
105
|
});
|
|
106
106
|
};
|
|
@@ -113,7 +113,7 @@ export class SecrecyCloudClient {
|
|
|
113
113
|
for (const [key, value] of Object.entries(part.fields)) {
|
|
114
114
|
formData.append(key, value);
|
|
115
115
|
}
|
|
116
|
-
formData.append('
|
|
116
|
+
formData.append('data', new Blob([chunk.data]), `${uploadData.dataId}-${chunk.order}`);
|
|
117
117
|
await axios.post(part.url, formData, {
|
|
118
118
|
onUploadProgress: (progressEvent) => {
|
|
119
119
|
onProgress(part.order, progressEvent);
|
|
@@ -126,12 +126,12 @@ export class SecrecyCloudClient {
|
|
|
126
126
|
await byPart(p);
|
|
127
127
|
}));
|
|
128
128
|
await uploadEnded();
|
|
129
|
-
|
|
129
|
+
lruCache.set(uploadData.dataId, dataBuffer);
|
|
130
130
|
return uploadData.dataId;
|
|
131
131
|
}
|
|
132
|
-
async uploadDataInCloud({
|
|
132
|
+
async uploadDataInCloud({ data, name, nodeId, encryptProgress, uploadProgress, signal, }) {
|
|
133
133
|
const dataId = await this.uploadData({
|
|
134
|
-
|
|
134
|
+
data,
|
|
135
135
|
encryptProgress,
|
|
136
136
|
uploadProgress,
|
|
137
137
|
signal,
|
|
@@ -220,11 +220,11 @@ export class SecrecyCloudClient {
|
|
|
220
220
|
});
|
|
221
221
|
return await apiNodeToExternalNodeFull(node, this.#keys);
|
|
222
222
|
}
|
|
223
|
-
async
|
|
224
|
-
const
|
|
223
|
+
async dataMetadata({ id }) {
|
|
224
|
+
const data = await this.#apiClient.cloud.dataById.query({
|
|
225
225
|
id,
|
|
226
226
|
});
|
|
227
|
-
return
|
|
227
|
+
return apiDataToExternal(data, this.#keys);
|
|
228
228
|
}
|
|
229
229
|
async shareNode({ nodeId, userId, rights, }) {
|
|
230
230
|
const publicKey = await this.#client.app.userPublicKey(userId);
|
|
@@ -270,26 +270,26 @@ export class SecrecyCloudClient {
|
|
|
270
270
|
});
|
|
271
271
|
return await apiNodeToExternalNodeFull(updateNode, this.#keys);
|
|
272
272
|
}
|
|
273
|
-
async
|
|
274
|
-
const cached =
|
|
273
|
+
async dataContent({ dataId, onDownloadProgress, progressDecrypt, signal, }) {
|
|
274
|
+
const cached = lruCache.get(dataId);
|
|
275
275
|
if (cached !== undefined) {
|
|
276
276
|
return cached;
|
|
277
277
|
}
|
|
278
|
-
const
|
|
279
|
-
id:
|
|
278
|
+
const dataContent = await this.#apiClient.cloud.dataContentById.query({
|
|
279
|
+
id: dataId,
|
|
280
280
|
});
|
|
281
281
|
const progressParts = {};
|
|
282
282
|
const onProgress = (part, progressEvent) => {
|
|
283
283
|
progressParts[part] = progressEvent;
|
|
284
284
|
const transferredBytes = Object.values(progressParts).reduce((prv, cur) => prv + cur.transferredBytes, 0);
|
|
285
|
-
const totalBytes = Number(
|
|
285
|
+
const totalBytes = Number(dataContent.totalSize);
|
|
286
286
|
onDownloadProgress?.({
|
|
287
287
|
percent: transferredBytes / totalBytes,
|
|
288
288
|
totalBytes,
|
|
289
289
|
transferredBytes,
|
|
290
290
|
});
|
|
291
291
|
};
|
|
292
|
-
const encryptedContentFromParts = async (
|
|
292
|
+
const encryptedContentFromParts = async (dataParts) => {
|
|
293
293
|
const parts = new Array();
|
|
294
294
|
const byPart = async (part) => {
|
|
295
295
|
const buf = new Uint8Array(await ky
|
|
@@ -303,14 +303,14 @@ export class SecrecyCloudClient {
|
|
|
303
303
|
.arrayBuffer());
|
|
304
304
|
const md5Part = await md5(buf);
|
|
305
305
|
if (md5Part !== part.md5) {
|
|
306
|
-
throw new Error(`Invalid md5 for part ${part.order} of
|
|
306
|
+
throw new Error(`Invalid md5 for part ${part.order} of data ${dataId}`);
|
|
307
307
|
}
|
|
308
308
|
parts.push({
|
|
309
309
|
data: buf,
|
|
310
310
|
order: part.order,
|
|
311
311
|
});
|
|
312
312
|
};
|
|
313
|
-
await promiseAllLimit(3,
|
|
313
|
+
await promiseAllLimit(3, dataParts.map((p) => async () => {
|
|
314
314
|
await byPart(p);
|
|
315
315
|
}));
|
|
316
316
|
return concatenate(...parts.sort((a, b) => a.order - b.order).map((p) => p.data));
|
|
@@ -318,40 +318,40 @@ export class SecrecyCloudClient {
|
|
|
318
318
|
const finalize = async (encryptedContent) => {
|
|
319
319
|
// const md5Encrypted = await firstValueFrom(md5(of(encryptedContent)));
|
|
320
320
|
const md5Encrypted = await md5(encryptedContent);
|
|
321
|
-
if (md5Encrypted !==
|
|
321
|
+
if (md5Encrypted !== dataContent.md5Encrypted) {
|
|
322
322
|
throw new Error(`Encrypted content does not match`);
|
|
323
323
|
}
|
|
324
|
-
const key = decryptCryptoBox(sodium.from_hex(
|
|
325
|
-
?
|
|
326
|
-
:
|
|
327
|
-
?
|
|
324
|
+
const key = decryptCryptoBox(sodium.from_hex(dataContent.key), dataContent.type === 'received_mail'
|
|
325
|
+
? dataContent.senderPublicKey
|
|
326
|
+
: dataContent.type === 'cloud'
|
|
327
|
+
? dataContent.publicKey
|
|
328
328
|
: this.#keys.publicKey, this.#keys.privateKey);
|
|
329
329
|
const src = await decrypt(key, encryptedContent, progressDecrypt, signal);
|
|
330
330
|
// const md5Content = await firstValueFrom(md5(of(src)));
|
|
331
331
|
const md5Content = await md5(src);
|
|
332
|
-
if (md5Content !==
|
|
332
|
+
if (md5Content !== dataContent.md5) {
|
|
333
333
|
throw new Error(`Content does not match`);
|
|
334
334
|
}
|
|
335
335
|
return decompress(src);
|
|
336
336
|
};
|
|
337
|
-
const encryptedContent =
|
|
338
|
-
?
|
|
339
|
-
:
|
|
340
|
-
? await encryptedContentFromParts(
|
|
337
|
+
const encryptedContent = dataContent.type === 'lite'
|
|
338
|
+
? dataContent.content
|
|
339
|
+
: dataContent.type === 'cloud'
|
|
340
|
+
? await encryptedContentFromParts(dataContent.parts)
|
|
341
341
|
: // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
|
|
342
|
-
|
|
343
|
-
?
|
|
344
|
-
:
|
|
345
|
-
? await encryptedContentFromParts(
|
|
342
|
+
dataContent.maybeContent !== null
|
|
343
|
+
? dataContent.maybeContent
|
|
344
|
+
: dataContent.maybeParts !== null
|
|
345
|
+
? await encryptedContentFromParts(dataContent.maybeParts)
|
|
346
346
|
: null;
|
|
347
347
|
if (encryptedContent === null) {
|
|
348
|
-
throw `Can't find content for
|
|
348
|
+
throw `Can't find content for data ${dataId}`;
|
|
349
349
|
}
|
|
350
|
-
const data = await finalize(
|
|
351
|
-
|
|
350
|
+
const data = await finalize(encryptedContent);
|
|
351
|
+
lruCache.set(dataId, data);
|
|
352
352
|
return data;
|
|
353
353
|
}
|
|
354
|
-
async
|
|
354
|
+
async deleteData({ dataId, nodeId, }) {
|
|
355
355
|
const { isDeleted } = await this.#apiClient.cloud.deleteData.mutate({
|
|
356
356
|
dataId,
|
|
357
357
|
nodeId,
|
|
@@ -394,40 +394,40 @@ export class SecrecyCloudClient {
|
|
|
394
394
|
}
|
|
395
395
|
}
|
|
396
396
|
let key = '';
|
|
397
|
-
const
|
|
398
|
-
if (
|
|
399
|
-
await this.
|
|
400
|
-
const
|
|
401
|
-
if (
|
|
397
|
+
const data = dataCache.get(dataId);
|
|
398
|
+
if (data === undefined) {
|
|
399
|
+
await this.dataMetadata({ id: dataId });
|
|
400
|
+
const data = dataCache.get(dataId);
|
|
401
|
+
if (data === undefined) {
|
|
402
402
|
const receivedMails = await this.#client.mail.receivedMails();
|
|
403
|
-
const mail = receivedMails.find((m) => m.
|
|
403
|
+
const mail = receivedMails.find((m) => m.attachments.some((f) => f.id === dataId));
|
|
404
404
|
if (mail === undefined) {
|
|
405
405
|
const err = {
|
|
406
406
|
name: 'ClientError',
|
|
407
407
|
code: 'NOT_FOUND',
|
|
408
|
-
message: `Can't find mail with the
|
|
408
|
+
message: `Can't find mail with the attachment ${dataId}`,
|
|
409
409
|
};
|
|
410
410
|
throw err;
|
|
411
411
|
}
|
|
412
|
-
const
|
|
413
|
-
if (
|
|
412
|
+
const attachment = mail.attachments.find((d) => d.id === dataId);
|
|
413
|
+
if (attachment === undefined) {
|
|
414
414
|
const err = {
|
|
415
415
|
name: 'ClientError',
|
|
416
416
|
code: 'NOT_FOUND',
|
|
417
|
-
message: `Can't find mail with the
|
|
417
|
+
message: `Can't find mail with the attachment ${dataId}`,
|
|
418
418
|
};
|
|
419
419
|
throw err;
|
|
420
420
|
}
|
|
421
421
|
const senderPubKey = await this.#client.app.userPublicKey(mail.sender.id);
|
|
422
|
-
const
|
|
423
|
-
key = sodium.to_hex(
|
|
422
|
+
const dataKey = decryptCryptoBox(sodium.from_hex(attachment.key), senderPubKey, this.#keys.privateKey);
|
|
423
|
+
key = sodium.to_hex(dataKey);
|
|
424
424
|
}
|
|
425
425
|
else {
|
|
426
|
-
key =
|
|
426
|
+
key = data.key;
|
|
427
427
|
}
|
|
428
428
|
}
|
|
429
429
|
else {
|
|
430
|
-
key =
|
|
430
|
+
key = data.key;
|
|
431
431
|
}
|
|
432
432
|
key = sodium.to_hex(encryptCryptoBox(sodium.from_hex(key), this.#keys.publicKey, this.#keys.privateKey));
|
|
433
433
|
const nameKey = secretStreamKeygen();
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { dataCache } from '../cache.js';
|
|
2
2
|
import { encryptCryptoBox, sodium } from '../index.js';
|
|
3
3
|
import { convertInternalMailToExternal } from './convert/mail.js';
|
|
4
4
|
export class SecrecyMailClient {
|
|
@@ -67,11 +67,11 @@ export class SecrecyMailClient {
|
|
|
67
67
|
}
|
|
68
68
|
if (senderFiles !== undefined) {
|
|
69
69
|
for (const f of senderFiles) {
|
|
70
|
-
let
|
|
71
|
-
if (
|
|
72
|
-
await this.#client.cloud.
|
|
73
|
-
|
|
74
|
-
if (
|
|
70
|
+
let data = dataCache.get(f.id);
|
|
71
|
+
if (data === undefined) {
|
|
72
|
+
await this.#client.cloud.dataMetadata({ id: f.id });
|
|
73
|
+
data = dataCache.get(f.id);
|
|
74
|
+
if (data === undefined) {
|
|
75
75
|
throw new Error(`File ${f.name} (${f.id}) does not exists`);
|
|
76
76
|
}
|
|
77
77
|
}
|
|
@@ -132,7 +132,7 @@ export class SecrecyMailClient {
|
|
|
132
132
|
if (email === undefined) {
|
|
133
133
|
continue;
|
|
134
134
|
}
|
|
135
|
-
const input = await this._eachUser(draft.
|
|
135
|
+
const input = await this._eachUser(draft.attachments, draft.subject, draft.body, email);
|
|
136
136
|
if (input === null) {
|
|
137
137
|
temporaryRecipients.push(email);
|
|
138
138
|
}
|
|
@@ -141,7 +141,7 @@ export class SecrecyMailClient {
|
|
|
141
141
|
}
|
|
142
142
|
}
|
|
143
143
|
for (const { id } of draft.recipients) {
|
|
144
|
-
const input = await this._eachUser(draft.
|
|
144
|
+
const input = await this._eachUser(draft.attachments, draft.subject, draft.body, id);
|
|
145
145
|
if (input === null) {
|
|
146
146
|
temporaryRecipients.push(id);
|
|
147
147
|
}
|
|
@@ -167,7 +167,7 @@ export class SecrecyMailClient {
|
|
|
167
167
|
continue;
|
|
168
168
|
}
|
|
169
169
|
try {
|
|
170
|
-
const input = await this._eachUser(mail.
|
|
170
|
+
const input = await this._eachUser(mail.attachments, mail.subject, mail.body, email);
|
|
171
171
|
if (input === null) {
|
|
172
172
|
continue;
|
|
173
173
|
}
|
|
@@ -175,13 +175,11 @@ export class SecrecyMailClient {
|
|
|
175
175
|
id: mail.mailIntegrityId,
|
|
176
176
|
recipient: {
|
|
177
177
|
...input,
|
|
178
|
-
attachments: input.attachments.map((
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
};
|
|
184
|
-
}),
|
|
178
|
+
attachments: input.attachments.map((attachment) => ({
|
|
179
|
+
id: attachment.dataId,
|
|
180
|
+
key: attachment.key,
|
|
181
|
+
name: attachment.name,
|
|
182
|
+
})),
|
|
185
183
|
},
|
|
186
184
|
});
|
|
187
185
|
}
|
|
@@ -195,16 +193,16 @@ export class SecrecyMailClient {
|
|
|
195
193
|
const hashKey = sodium.randombytes_buf(sodium.crypto_generichash_KEYBYTES, 'hex');
|
|
196
194
|
const hash = sodium.crypto_generichash(sodium.crypto_generichash_BYTES, JSON.stringify({ body, subject }), hashKey, 'hex');
|
|
197
195
|
for (const f of senderFiles) {
|
|
198
|
-
let
|
|
199
|
-
if (
|
|
200
|
-
await this.#client.cloud.
|
|
201
|
-
|
|
202
|
-
if (
|
|
196
|
+
let data = dataCache.get(f.id);
|
|
197
|
+
if (data === undefined) {
|
|
198
|
+
await this.#client.cloud.dataMetadata({ id: f.id });
|
|
199
|
+
data = dataCache.get(f.id);
|
|
200
|
+
if (data === undefined) {
|
|
203
201
|
throw new Error(`File ${f.name} (${f.id}) does not exists`);
|
|
204
202
|
}
|
|
205
203
|
}
|
|
206
204
|
senderFiles.push({
|
|
207
|
-
id:
|
|
205
|
+
id: data.id,
|
|
208
206
|
name: sodium.to_hex(encryptCryptoBox(sodium.from_string(f.name), this.#keys.publicKey, this.#keys.privateKey)),
|
|
209
207
|
});
|
|
210
208
|
}
|
|
@@ -263,22 +261,22 @@ export class SecrecyMailClient {
|
|
|
263
261
|
const unreadReceivedMailsCount = await this.#apiClient.mail.unreadReceivedCount.query({});
|
|
264
262
|
return unreadReceivedMailsCount;
|
|
265
263
|
}
|
|
266
|
-
_eachUser = async (
|
|
264
|
+
_eachUser = async (data, subject, body, idOrMail) => {
|
|
267
265
|
// const pubKey = await this.#client.app.userPublicKey(userId)
|
|
268
266
|
const pubKey = await this.#client.app.userPublicKey(idOrMail);
|
|
269
|
-
// const
|
|
270
|
-
const
|
|
271
|
-
for (const f of
|
|
272
|
-
let
|
|
273
|
-
if (
|
|
274
|
-
await this.#client.cloud.
|
|
275
|
-
|
|
276
|
-
if (
|
|
267
|
+
// const attachments = new Array<MailFileInput>()
|
|
268
|
+
const attachments = new Array();
|
|
269
|
+
for (const f of data) {
|
|
270
|
+
let dataInHistory = dataCache.get(f.id);
|
|
271
|
+
if (dataInHistory === undefined) {
|
|
272
|
+
await this.#client.cloud.dataMetadata({ id: f.id });
|
|
273
|
+
dataInHistory = dataCache.get(f.id);
|
|
274
|
+
if (dataInHistory === undefined) {
|
|
277
275
|
throw new Error(`File ${f.name} (${f.id}) does not exists`);
|
|
278
276
|
}
|
|
279
277
|
}
|
|
280
|
-
const key =
|
|
281
|
-
|
|
278
|
+
const key = dataInHistory.key;
|
|
279
|
+
attachments.push({
|
|
282
280
|
id: f.id,
|
|
283
281
|
name: sodium.to_hex(encryptCryptoBox(sodium.from_string(f.name), pubKey, this.#keys.privateKey)),
|
|
284
282
|
key: sodium.to_hex(encryptCryptoBox(sodium.from_hex(key), pubKey, this.#keys.privateKey)),
|
|
@@ -289,7 +287,7 @@ export class SecrecyMailClient {
|
|
|
289
287
|
recipientId: idOrMail,
|
|
290
288
|
body: sodium.to_hex(encryptCryptoBox(sodium.from_string(body), pubKey, this.#keys.privateKey)),
|
|
291
289
|
subject: sodium.to_hex(encryptCryptoBox(sodium.from_string(subject), pubKey, this.#keys.privateKey)),
|
|
292
|
-
attachments
|
|
290
|
+
attachments,
|
|
293
291
|
};
|
|
294
292
|
};
|
|
295
293
|
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { dataCache } from '../../cache.js';
|
|
2
|
+
import { decryptCryptoBox } from '../../crypto/index.js';
|
|
3
|
+
import { sodium } from '../../sodium.js';
|
|
4
|
+
export function apiDataToInternal(apiData, keyPair) {
|
|
5
|
+
const data = {
|
|
6
|
+
id: apiData.id,
|
|
7
|
+
md5: apiData.md5,
|
|
8
|
+
md5Encrypted: apiData.md5Encrypted,
|
|
9
|
+
createdAt: apiData.createdAt,
|
|
10
|
+
size: apiData.size,
|
|
11
|
+
sizeBefore: apiData.sizeBefore,
|
|
12
|
+
key: sodium.to_hex(decryptCryptoBox(sodium.from_hex(apiData.access.key), apiData.access.sharedByPubKey, keyPair.privateKey)),
|
|
13
|
+
};
|
|
14
|
+
dataCache.set(data.id, data);
|
|
15
|
+
return data;
|
|
16
|
+
}
|
|
17
|
+
export function internalDataToExternalData(internal) {
|
|
18
|
+
return {
|
|
19
|
+
id: internal.id,
|
|
20
|
+
md5: internal.md5,
|
|
21
|
+
md5Encrypted: internal.md5Encrypted,
|
|
22
|
+
createdAt: internal.createdAt,
|
|
23
|
+
size: internal.size,
|
|
24
|
+
sizeBefore: internal.sizeBefore,
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
export function apiDataToExternal(apiData, keyPair) {
|
|
28
|
+
return internalDataToExternalData(apiDataToInternal(apiData, keyPair));
|
|
29
|
+
}
|
|
@@ -30,12 +30,12 @@ export async function convertInternalMailToExternal({ client, mail, keyPair, })
|
|
|
30
30
|
temporaryRecipients: [], // TODO
|
|
31
31
|
recipients: mI.recipients,
|
|
32
32
|
sender: mail.sender,
|
|
33
|
-
|
|
34
|
-
const name = sodium.to_string(decryptCryptoBox(sodium.from_hex(
|
|
33
|
+
attachments: mail.attachments.map((f) => {
|
|
34
|
+
const name = sodium.to_string(decryptCryptoBox(sodium.from_hex(f.name), pubKey, privateKey));
|
|
35
35
|
return {
|
|
36
|
-
id:
|
|
36
|
+
id: f.dataId,
|
|
37
37
|
name,
|
|
38
|
-
key:
|
|
38
|
+
key: f.key,
|
|
39
39
|
};
|
|
40
40
|
}),
|
|
41
41
|
};
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { sodium } from '../../sodium.js';
|
|
2
2
|
import { decryptCryptoBox } from '../../crypto/index.js';
|
|
3
3
|
import { nodesCache } from '../../cache.js';
|
|
4
|
-
import { decryptSecretStream } from '../../crypto/
|
|
5
|
-
import {
|
|
4
|
+
import { decryptSecretStream } from '../../crypto/data.js';
|
|
5
|
+
import { apiDataToInternal, internalDataToExternalData } from './data.js';
|
|
6
6
|
export async function apiNodeToInternal(apiNode, keyPair) {
|
|
7
7
|
const internal = {
|
|
8
8
|
id: apiNode.id,
|
|
@@ -23,7 +23,7 @@ export async function apiNodeToInternal(apiNode, keyPair) {
|
|
|
23
23
|
deletedAt: apiNode.deletedAt,
|
|
24
24
|
users: apiNode.users,
|
|
25
25
|
parentId: apiNode.parentId ?? null,
|
|
26
|
-
|
|
26
|
+
currentDataId: apiNode.currentDataId ?? null,
|
|
27
27
|
};
|
|
28
28
|
internal.access = { ...apiNode.access };
|
|
29
29
|
if (apiNode.access.nameKey !== null) {
|
|
@@ -47,13 +47,13 @@ export async function apiNodeFullToInternalFull(apiNodeFull, keyPair) {
|
|
|
47
47
|
return {
|
|
48
48
|
...f,
|
|
49
49
|
current: apiNodeFull.current !== null
|
|
50
|
-
?
|
|
50
|
+
? apiDataToInternal(apiNodeFull.current, keyPair)
|
|
51
51
|
: undefined,
|
|
52
52
|
parent: apiNodeFull.parent !== null
|
|
53
53
|
? await apiNodeToInternal(apiNodeFull.parent, keyPair)
|
|
54
54
|
: null,
|
|
55
55
|
children: await Promise.all(apiNodeFull.children.map(async (s) => await apiNodeToInternal(s, keyPair))),
|
|
56
|
-
history: apiNodeFull.history.map((f) =>
|
|
56
|
+
history: apiNodeFull.history.map((f) => apiDataToInternal(f, keyPair)),
|
|
57
57
|
};
|
|
58
58
|
}
|
|
59
59
|
export function internalNodeToNode(internal) {
|
|
@@ -76,9 +76,9 @@ export function internalNodeFullToNodeFull(internal) {
|
|
|
76
76
|
...internalNodeToNode(internal),
|
|
77
77
|
parent: internal.parent !== null ? internalNodeToNode(internal.parent) : null,
|
|
78
78
|
children: internal.children.map(internalNodeToNode),
|
|
79
|
-
history: internal.history.map((f) =>
|
|
79
|
+
history: internal.history.map((f) => internalDataToExternalData(f)),
|
|
80
80
|
current: internal.current !== null && internal.current !== undefined
|
|
81
|
-
?
|
|
81
|
+
? internalDataToExternalData(internal.current)
|
|
82
82
|
: undefined,
|
|
83
83
|
};
|
|
84
84
|
}
|
package/dist/lib/client/index.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { BaseClient } from '../base-client.js';
|
|
2
|
-
import { encryptSecretStream } from '../crypto/
|
|
2
|
+
import { encryptSecretStream } from '../crypto/data.js';
|
|
3
3
|
import { sodium } from '../sodium.js';
|
|
4
4
|
import { SecrecyCloudClient } from './SecrecyCloudClient.js';
|
|
5
5
|
import { SecrecyMailClient } from './SecrecyMailClient.js';
|
|
6
6
|
import { SecrecyAppClient } from './SecrecyAppClient.js';
|
|
7
|
-
import { nodesCache,
|
|
7
|
+
import { nodesCache, dataCache, publicKeysCache } from '../cache.js';
|
|
8
8
|
import { SecrecyDbClient } from './SecrecyDbClient.js';
|
|
9
9
|
import { SecrecyWalletClient } from './SecrecyWalletClient.js';
|
|
10
10
|
import { SecrecyPayClient } from './SecrecyPayClient.js';
|
|
@@ -53,7 +53,7 @@ export class SecrecyClient extends BaseClient {
|
|
|
53
53
|
}
|
|
54
54
|
async logout(sessionId) {
|
|
55
55
|
nodesCache.clear();
|
|
56
|
-
|
|
56
|
+
dataCache.clear();
|
|
57
57
|
publicKeysCache.clear();
|
|
58
58
|
await super.logout(sessionId);
|
|
59
59
|
}
|
package/dist/types/cache.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import type { InternalNode,
|
|
1
|
+
import type { InternalNode, InternalData, InternalNodeFull } from './client/types/index.js';
|
|
2
2
|
import { LRUCache } from 'lru-cache';
|
|
3
|
-
export declare const
|
|
3
|
+
export declare const dataCache: Map<string, InternalData>;
|
|
4
4
|
export declare const nodesCache: Map<string, InternalNode | InternalNodeFull>;
|
|
5
5
|
export declare const usersCache: Map<string, {
|
|
6
6
|
id: string;
|
|
@@ -10,4 +10,4 @@ export declare const usersCache: Map<string, {
|
|
|
10
10
|
isSearchable: boolean;
|
|
11
11
|
}>;
|
|
12
12
|
export declare const publicKeysCache: Map<string, string>;
|
|
13
|
-
export declare const
|
|
13
|
+
export declare const lruCache: LRUCache<string, Uint8Array, unknown>;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { ProgressCallback, SecrecyClient } from '../index.js';
|
|
2
|
-
import type {
|
|
2
|
+
import type { DataMetadata, KeyPair, Node, NodeFull, NodeType, Rights } from './types/index.js';
|
|
3
3
|
import { type ApiClient } from '../client.js';
|
|
4
4
|
import { type DownloadProgress } from '../types.js';
|
|
5
5
|
export declare class SecrecyCloudClient {
|
|
@@ -9,14 +9,14 @@ export declare class SecrecyCloudClient {
|
|
|
9
9
|
dataId: string;
|
|
10
10
|
nodeId: string;
|
|
11
11
|
}): Promise<NodeFull>;
|
|
12
|
-
uploadData({
|
|
13
|
-
|
|
12
|
+
uploadData({ data, encryptProgress, uploadProgress, signal, }: {
|
|
13
|
+
data: globalThis.File | Uint8Array;
|
|
14
14
|
encryptProgress?: ProgressCallback;
|
|
15
15
|
uploadProgress?: ProgressCallback;
|
|
16
16
|
signal?: AbortSignal;
|
|
17
17
|
}): Promise<string>;
|
|
18
|
-
uploadDataInCloud({
|
|
19
|
-
|
|
18
|
+
uploadDataInCloud({ data, name, nodeId, encryptProgress, uploadProgress, signal, }: {
|
|
19
|
+
data: globalThis.File | Uint8Array;
|
|
20
20
|
name: string;
|
|
21
21
|
nodeId?: string;
|
|
22
22
|
encryptProgress?: ProgressCallback;
|
|
@@ -46,9 +46,9 @@ export declare class SecrecyCloudClient {
|
|
|
46
46
|
id?: string | null | undefined;
|
|
47
47
|
deleted?: boolean | null | undefined;
|
|
48
48
|
}): Promise<NodeFull>;
|
|
49
|
-
|
|
49
|
+
dataMetadata({ id }: {
|
|
50
50
|
id: string;
|
|
51
|
-
}): Promise<
|
|
51
|
+
}): Promise<DataMetadata>;
|
|
52
52
|
shareNode({ nodeId, userId, rights, }: {
|
|
53
53
|
nodeId: string;
|
|
54
54
|
userId: string;
|
|
@@ -60,13 +60,13 @@ export declare class SecrecyCloudClient {
|
|
|
60
60
|
isFavorite?: boolean | null | undefined;
|
|
61
61
|
deletedAt?: Date | null | undefined;
|
|
62
62
|
}): Promise<NodeFull>;
|
|
63
|
-
|
|
64
|
-
|
|
63
|
+
dataContent({ dataId, onDownloadProgress, progressDecrypt, signal, }: {
|
|
64
|
+
dataId: string;
|
|
65
65
|
onDownloadProgress?: (progress: DownloadProgress) => void;
|
|
66
66
|
progressDecrypt?: ProgressCallback;
|
|
67
67
|
signal?: AbortSignal;
|
|
68
68
|
}): Promise<Uint8Array>;
|
|
69
|
-
|
|
69
|
+
deleteData({ dataId, nodeId, }: {
|
|
70
70
|
dataId: string;
|
|
71
71
|
nodeId: string;
|
|
72
72
|
}): Promise<boolean>;
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { ApiData, InternalData, DataMetadata, KeyPair } from '../types/index.js';
|
|
2
|
+
export declare function apiDataToInternal(apiData: ApiData, keyPair: KeyPair): InternalData;
|
|
3
|
+
export declare function internalDataToExternalData(internal: InternalData): DataMetadata;
|
|
4
|
+
export declare function apiDataToExternal(apiData: ApiData, keyPair: KeyPair): DataMetadata;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { BaseClient } from '../base-client.js';
|
|
2
|
-
import type { Progress } from '../crypto/
|
|
2
|
+
import type { Progress } from '../crypto/data.js';
|
|
3
3
|
import { SecrecyCloudClient } from './SecrecyCloudClient.js';
|
|
4
4
|
import { SecrecyMailClient } from './SecrecyMailClient.js';
|
|
5
5
|
import { SecrecyAppClient } from './SecrecyAppClient.js';
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { type RouterOutputs } from '../../client.js';
|
|
2
|
+
export type DataMetadata = Pick<ApiData, 'id' | 'size' | 'sizeBefore' | 'md5' | 'md5Encrypted' | 'createdAt'>;
|
|
3
|
+
export type InternalData = DataMetadata & {
|
|
4
|
+
key: string;
|
|
5
|
+
};
|
|
6
|
+
export type ApiData = NonNullable<RouterOutputs['cloud']['dataById']>;
|
|
@@ -15,7 +15,7 @@ export interface BaseMail {
|
|
|
15
15
|
isAltered: boolean;
|
|
16
16
|
recipients: Array<Omit<PublicUser, 'publicKey'>>;
|
|
17
17
|
temporaryRecipients: TemporaryMailUser[];
|
|
18
|
-
|
|
18
|
+
attachments: Array<{
|
|
19
19
|
id: string;
|
|
20
20
|
name: string;
|
|
21
21
|
key: string;
|
|
@@ -74,10 +74,10 @@ export interface WaitingReceivedMail {
|
|
|
74
74
|
recipients: PublicUser[];
|
|
75
75
|
temporaryRecipients: TemporaryMailUser[];
|
|
76
76
|
}
|
|
77
|
-
export interface
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
77
|
+
export interface MailData {
|
|
78
|
+
dataKey: string;
|
|
79
|
+
dataName: string;
|
|
80
|
+
data: {
|
|
81
81
|
id: string;
|
|
82
82
|
};
|
|
83
83
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { type RouterOutputs } from '../../client.js';
|
|
2
|
-
import type {
|
|
2
|
+
import type { DataMetadata, InternalData, PublicUser } from './index.js';
|
|
3
3
|
export type Rights = ApiNode['users'][number][1];
|
|
4
4
|
export type NodeAccess<T extends Record<string, unknown> = Record<string, unknown>> = T & {
|
|
5
5
|
rights: Rights;
|
|
@@ -34,17 +34,17 @@ export interface Node<T extends NodeBreadcrumbItem = NodeBreadcrumbItem, U exten
|
|
|
34
34
|
createdBy: PublicUser;
|
|
35
35
|
access: NodeAccess<U>;
|
|
36
36
|
users: Array<[PublicUser, Rights]>;
|
|
37
|
-
|
|
37
|
+
currentDataId: string | null;
|
|
38
38
|
parentId: string | null;
|
|
39
39
|
}
|
|
40
|
-
export type NodeFull<T extends NodeBreadcrumbItem = NodeBreadcrumbItem, U extends Record<string, unknown> = Record<string, unknown>, V extends
|
|
40
|
+
export type NodeFull<T extends NodeBreadcrumbItem = NodeBreadcrumbItem, U extends Record<string, unknown> = Record<string, unknown>, V extends DataMetadata = DataMetadata> = Node<T, U> & {
|
|
41
41
|
parent: Node<T, U> | null;
|
|
42
42
|
children: Array<Node<T, U>>;
|
|
43
43
|
current?: V;
|
|
44
44
|
history: V[];
|
|
45
45
|
};
|
|
46
46
|
export type InternalNode = Node<InternalNodeBreadcrumbItem, NameKey>;
|
|
47
|
-
export type InternalNodeFull = NodeFull<InternalNodeBreadcrumbItem, NameKey,
|
|
47
|
+
export type InternalNodeFull = NodeFull<InternalNodeBreadcrumbItem, NameKey, InternalData>;
|
|
48
48
|
export type ApiNode = RouterOutputs['cloud']['nodeById'];
|
|
49
49
|
export type ApiNodeFull = RouterOutputs['cloud']['nodeFullById'];
|
|
50
50
|
export type ApiNodeParent = NonNullable<RouterOutputs['cloud']['nodeFullById']['parent']>;
|
package/dist/types/client.d.ts
CHANGED
|
@@ -937,13 +937,13 @@ export declare const createTRPCClient: (session?: string | null | undefined, onA
|
|
|
937
937
|
};
|
|
938
938
|
_input_in: {
|
|
939
939
|
cloudNodeDaysForDelete?: number | null | undefined;
|
|
940
|
-
|
|
941
|
-
|
|
940
|
+
historyDataMaxCount?: number | null | undefined;
|
|
941
|
+
historyDataDaysForDelete?: number | null | undefined;
|
|
942
942
|
};
|
|
943
943
|
_input_out: {
|
|
944
944
|
cloudNodeDaysForDelete?: number | null | undefined;
|
|
945
|
-
|
|
946
|
-
|
|
945
|
+
historyDataMaxCount?: number | null | undefined;
|
|
946
|
+
historyDataDaysForDelete?: number | null | undefined;
|
|
947
947
|
};
|
|
948
948
|
_output_in: {
|
|
949
949
|
id: string;
|
package/dist/types/index.d.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
export * from './client/index.js';
|
|
2
2
|
export * from './crypto/index.js';
|
|
3
|
-
export type { Progress } from './crypto/
|
|
3
|
+
export type { Progress } from './crypto/data.js';
|
|
4
4
|
export { BaseClient } from './base-client.js';
|
|
5
|
-
export type { Node, NodeFull,
|
|
5
|
+
export type { Node, NodeFull, DataMetadata, SecrecyUserApp, BaseMail, ReceivedMail, SentMail, PublicUser, SelfUser, NodeSize, MailData, MailIntegrity, DraftMail, Mail, WaitingReceivedMail, KeyPair, } from './client/types/index.js';
|
|
6
6
|
export * from './client/helpers.js';
|
|
7
7
|
export * from './sodium.js';
|
|
8
8
|
export * from './utils/store-buddy.js';
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import type { EncryptedFile, Progress } from '../crypto/
|
|
1
|
+
import type { EncryptedFile, Progress } from '../crypto/data.js';
|
|
2
2
|
export declare function encrypt(key: Uint8Array, dataToEncrypt: Uint8Array, progress?: (progress: Progress) => Promise<void>, signal?: AbortSignal): Promise<EncryptedFile>;
|
|
3
3
|
export declare function decrypt(key: Uint8Array, dataToDecrypt: Uint8Array, progress?: (progress: Progress) => Promise<void>, signal?: AbortSignal): Promise<Uint8Array>;
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "@secrecy/lib",
|
|
3
3
|
"author": "Anonymize <anonymize@gmail.com>",
|
|
4
4
|
"description": "Anonymize Secrecy Library",
|
|
5
|
-
"version": "1.
|
|
5
|
+
"version": "1.30.0-feat-rename-file.1",
|
|
6
6
|
"repository": {
|
|
7
7
|
"type": "git",
|
|
8
8
|
"url": "https://github.com/anonymize-org/lib.git"
|
|
@@ -74,7 +74,7 @@
|
|
|
74
74
|
},
|
|
75
75
|
"dependencies": {
|
|
76
76
|
"@secrecy/lib-utils": "^1.0.18",
|
|
77
|
-
"@secrecy/trpc-api-types": "1.27.0-feat-rename-file.
|
|
77
|
+
"@secrecy/trpc-api-types": "1.27.0-feat-rename-file.13",
|
|
78
78
|
"@trpc/client": "10.45.2",
|
|
79
79
|
"@trpc/server": "10.45.2",
|
|
80
80
|
"@types/libsodium-wrappers-sumo": "^0.7.8",
|
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
import { filesCache } from '../../cache.js';
|
|
2
|
-
import { decryptCryptoBox } from '../../crypto/index.js';
|
|
3
|
-
import { sodium } from '../../sodium.js';
|
|
4
|
-
export function apiFileToInternal(apiFile, keyPair) {
|
|
5
|
-
const file = {
|
|
6
|
-
id: apiFile.id,
|
|
7
|
-
md5: apiFile.md5,
|
|
8
|
-
md5Encrypted: apiFile.md5Encrypted,
|
|
9
|
-
createdAt: apiFile.createdAt,
|
|
10
|
-
size: apiFile.size,
|
|
11
|
-
sizeBefore: apiFile.sizeBefore,
|
|
12
|
-
key: sodium.to_hex(decryptCryptoBox(sodium.from_hex(apiFile.access.key), apiFile.access.sharedByPubKey, keyPair.privateKey)),
|
|
13
|
-
};
|
|
14
|
-
filesCache.set(file.id, file);
|
|
15
|
-
return file;
|
|
16
|
-
}
|
|
17
|
-
export function internalFileToFile(internal) {
|
|
18
|
-
return {
|
|
19
|
-
id: internal.id,
|
|
20
|
-
md5: internal.md5,
|
|
21
|
-
md5Encrypted: internal.md5Encrypted,
|
|
22
|
-
createdAt: internal.createdAt,
|
|
23
|
-
size: internal.size,
|
|
24
|
-
sizeBefore: internal.sizeBefore,
|
|
25
|
-
};
|
|
26
|
-
}
|
|
27
|
-
export function apiFileToExternal(apiFile, keyPair) {
|
|
28
|
-
return internalFileToFile(apiFileToInternal(apiFile, keyPair));
|
|
29
|
-
}
|
|
@@ -1,4 +0,0 @@
|
|
|
1
|
-
import type { ApiFile, InternalFile, FileMetadata, KeyPair } from '../types/index.js';
|
|
2
|
-
export declare function apiFileToInternal(apiFile: ApiFile, keyPair: KeyPair): InternalFile;
|
|
3
|
-
export declare function internalFileToFile(internal: InternalFile): FileMetadata;
|
|
4
|
-
export declare function apiFileToExternal(apiFile: ApiFile, keyPair: KeyPair): FileMetadata;
|
|
@@ -1,6 +0,0 @@
|
|
|
1
|
-
import { type RouterOutputs } from '../../client.js';
|
|
2
|
-
export type FileMetadata = Pick<ApiFile, 'id' | 'size' | 'sizeBefore' | 'md5' | 'md5Encrypted' | 'createdAt'>;
|
|
3
|
-
export type InternalFile = FileMetadata & {
|
|
4
|
-
key: string;
|
|
5
|
-
};
|
|
6
|
-
export type ApiFile = NonNullable<RouterOutputs['cloud']['dataById']>;
|
|
File without changes
|
|
File without changes
|
|
File without changes
|