@secrecy/lib 1.7.0 → 1.8.0

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.
Files changed (152) hide show
  1. package/LICENSE +1 -1
  2. package/dist/lib/base-client.js +91 -0
  3. package/dist/lib/cache.js +4 -0
  4. package/dist/lib/client/SecrecryCareClient.js +19 -0
  5. package/dist/lib/client/SecrecyAppClient.js +87 -0
  6. package/dist/lib/client/SecrecyCloudClient.js +475 -0
  7. package/dist/lib/client/SecrecyDbClient.js +10 -0
  8. package/dist/lib/client/SecrecyMailClient.js +283 -0
  9. package/dist/lib/client/SecrecyPayClient.js +34 -0
  10. package/dist/lib/client/SecrecyUserClient.js +27 -0
  11. package/dist/lib/client/SecrecyWalletClient.js +50 -0
  12. package/dist/lib/client/convert/file.js +29 -0
  13. package/dist/lib/client/convert/mail.js +42 -0
  14. package/dist/lib/client/convert/node.js +100 -0
  15. package/dist/lib/client/helpers.js +103 -0
  16. package/dist/lib/client/index.js +49 -0
  17. package/dist/lib/client/storage.js +7 -0
  18. package/dist/lib/client/types/app.js +1 -0
  19. package/dist/lib/client/types/file.js +1 -0
  20. package/dist/lib/client/types/index.js +15 -0
  21. package/dist/lib/client/types/mail.js +1 -0
  22. package/dist/lib/client/types/node.js +1 -0
  23. package/dist/lib/client/types/user.js +1 -0
  24. package/dist/lib/client.js +47 -0
  25. package/dist/lib/crypto/file.js +184 -0
  26. package/dist/lib/crypto/index.js +41 -0
  27. package/dist/lib/error/client.js +10 -0
  28. package/dist/lib/error/index.js +10 -0
  29. package/dist/lib/error/server.js +27 -0
  30. package/dist/lib/index.js +10 -0
  31. package/dist/lib/minify/index.js +23 -0
  32. package/dist/lib/minify/lz4.js +517 -0
  33. package/dist/lib/sodium.js +5 -0
  34. package/dist/lib/types.js +1 -0
  35. package/dist/lib/utils/array.js +25 -0
  36. package/dist/lib/utils/base64.js +2 -0
  37. package/dist/lib/utils/popup-tools.js +180 -0
  38. package/dist/lib/utils/promise.js +20 -0
  39. package/dist/lib/utils/store-buddy.js +60 -0
  40. package/dist/lib/utils/time.js +13 -0
  41. package/dist/lib/worker/md5.js +18 -0
  42. package/dist/lib/worker/sodium.js +95 -0
  43. package/dist/lib/worker/workerCodes.js +254 -0
  44. package/dist/types/base-client.d.ts +28 -0
  45. package/dist/types/cache.d.ts +11 -0
  46. package/dist/types/client/SecrecryCareClient.d.ts +9 -0
  47. package/dist/types/client/SecrecyAppClient.d.ts +20 -0
  48. package/dist/types/client/SecrecyCloudClient.d.ts +88 -0
  49. package/dist/types/client/SecrecyDbClient.d.ts +7 -0
  50. package/dist/types/client/SecrecyMailClient.d.ts +42 -0
  51. package/dist/types/client/SecrecyPayClient.d.ts +27 -0
  52. package/dist/types/client/SecrecyUserClient.d.ts +14 -0
  53. package/dist/{client → types/client}/SecrecyWalletClient.d.ts +9 -12
  54. package/dist/types/client/convert/file.d.ts +4 -0
  55. package/dist/types/client/convert/mail.d.ts +8 -0
  56. package/dist/types/client/convert/node.d.ts +7 -0
  57. package/dist/types/client/helpers.d.ts +26 -0
  58. package/dist/types/client/index.d.ts +29 -0
  59. package/dist/{client → types/client}/storage.d.ts +2 -2
  60. package/dist/types/client/types/app.d.ts +2 -0
  61. package/dist/types/client/types/file.d.ts +6 -0
  62. package/dist/types/client/types/index.d.ts +46 -0
  63. package/dist/types/client/types/mail.d.ts +87 -0
  64. package/dist/{client/types/Node.d.ts → types/client/types/node.d.ts} +20 -25
  65. package/dist/types/client/types/user.d.ts +3 -0
  66. package/dist/types/client.d.ts +12138 -0
  67. package/dist/{crypto → types/crypto}/file.d.ts +4 -4
  68. package/dist/{crypto → types/crypto}/index.d.ts +1 -4
  69. package/dist/types/error/client.d.ts +22 -0
  70. package/dist/types/error/index.d.ts +40 -0
  71. package/dist/types/error/server.d.ts +26 -0
  72. package/dist/types/index.d.ts +13 -0
  73. package/dist/{minify → types/minify}/index.d.ts +1 -1
  74. package/dist/types/minify/lz4.d.ts +35 -0
  75. package/dist/{sodium.d.ts → types/sodium.d.ts} +1 -1
  76. package/dist/types/types.d.ts +10 -0
  77. package/dist/{utils/utils.d.ts → types/utils/array.d.ts} +0 -1
  78. package/dist/types/utils/base64.d.ts +1 -0
  79. package/dist/types/utils/popup-tools.d.ts +52 -0
  80. package/dist/types/utils/promise.d.ts +1 -0
  81. package/dist/types/utils/store-buddy.d.ts +130 -0
  82. package/dist/{worker → types/worker}/sodium.d.ts +1 -1
  83. package/package.json +48 -73
  84. package/dist/BaseClient.d.ts +0 -111
  85. package/dist/BaseClient.js +0 -506
  86. package/dist/PopupTools.d.ts +0 -17
  87. package/dist/PopupTools.js +0 -195
  88. package/dist/ZeusThunder.d.ts +0 -2
  89. package/dist/ZeusThunder.js +0 -65
  90. package/dist/cache.d.ts +0 -6
  91. package/dist/cache.js +0 -4
  92. package/dist/client/SecrecyAppClient.d.ts +0 -17
  93. package/dist/client/SecrecyAppClient.js +0 -226
  94. package/dist/client/SecrecyCloudClient.d.ts +0 -89
  95. package/dist/client/SecrecyCloudClient.js +0 -1405
  96. package/dist/client/SecrecyDbClient.d.ts +0 -48
  97. package/dist/client/SecrecyDbClient.js +0 -419
  98. package/dist/client/SecrecyMailClient.d.ts +0 -42
  99. package/dist/client/SecrecyMailClient.js +0 -1022
  100. package/dist/client/SecrecyPayClient.d.ts +0 -28
  101. package/dist/client/SecrecyPayClient.js +0 -68
  102. package/dist/client/SecrecyWalletClient.js +0 -73
  103. package/dist/client/convert/file.d.ts +0 -5
  104. package/dist/client/convert/file.js +0 -33
  105. package/dist/client/convert/mail.d.ts +0 -3
  106. package/dist/client/convert/mail.js +0 -42
  107. package/dist/client/convert/node.d.ts +0 -9
  108. package/dist/client/convert/node.js +0 -87
  109. package/dist/client/helpers.d.ts +0 -28
  110. package/dist/client/helpers.js +0 -119
  111. package/dist/client/index.d.ts +0 -34
  112. package/dist/client/index.js +0 -46
  113. package/dist/client/storage.js +0 -12
  114. package/dist/client/types/File.d.ts +0 -14
  115. package/dist/client/types/File.js +0 -3
  116. package/dist/client/types/Inputs.d.ts +0 -16
  117. package/dist/client/types/Inputs.js +0 -3
  118. package/dist/client/types/Node.js +0 -3
  119. package/dist/client/types/UserAppNotifications.d.ts +0 -6
  120. package/dist/client/types/UserAppNotifications.js +0 -3
  121. package/dist/client/types/UserAppSettings.d.ts +0 -5
  122. package/dist/client/types/UserAppSettings.js +0 -3
  123. package/dist/client/types/index.d.ts +0 -120
  124. package/dist/client/types/index.js +0 -8
  125. package/dist/client/types/selectors.d.ts +0 -400
  126. package/dist/client/types/selectors.js +0 -135
  127. package/dist/crypto/file.js +0 -195
  128. package/dist/crypto/index.js +0 -45
  129. package/dist/error.d.ts +0 -33
  130. package/dist/error.js +0 -3
  131. package/dist/index.d.ts +0 -14
  132. package/dist/index.js +0 -10
  133. package/dist/minify/index.js +0 -23
  134. package/dist/minify/lz4.d.ts +0 -5
  135. package/dist/minify/lz4.js +0 -539
  136. package/dist/sodium.js +0 -6
  137. package/dist/utils/encoders.d.ts +0 -26
  138. package/dist/utils/encoders.js +0 -18
  139. package/dist/utils/store-buddy.d.ts +0 -14
  140. package/dist/utils/store-buddy.js +0 -58
  141. package/dist/utils/time.js +0 -12
  142. package/dist/utils/utils.js +0 -47
  143. package/dist/worker/md5.js +0 -24
  144. package/dist/worker/sodium.js +0 -118
  145. package/dist/worker/workerCodes.js +0 -255
  146. package/dist/zeus/const.d.ts +0 -7
  147. package/dist/zeus/const.js +0 -1846
  148. package/dist/zeus/index.d.ts +0 -8721
  149. package/dist/zeus/index.js +0 -642
  150. /package/dist/{utils → types/utils}/time.d.ts +0 -0
  151. /package/dist/{worker → types/worker}/md5.d.ts +0 -0
  152. /package/dist/{worker → types/worker}/workerCodes.d.ts +0 -0
package/LICENSE CHANGED
@@ -1,6 +1,6 @@
1
1
  MIT License
2
2
 
3
- Copyright (c) 2023 Anonymize
3
+ Copyright (c) 2024 Anonymize
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
@@ -0,0 +1,91 @@
1
+ import { getLink } from '@secrecy/lib-utils';
2
+ import { usersCache } from './cache.js';
3
+ import { getStorage } from './client/storage.js';
4
+ import { createTRPCClient, } from './client.js';
5
+ async function getPublicUser(client, id) {
6
+ let user = usersCache.get(id);
7
+ if (user !== undefined) {
8
+ return user;
9
+ }
10
+ user = await client.user.byId.query({
11
+ id,
12
+ });
13
+ usersCache.set(user.id, user);
14
+ return user;
15
+ }
16
+ export class BaseClient {
17
+ static getBaseClient = (session, onAccessDenied) => createTRPCClient(session, onAccessDenied);
18
+ client;
19
+ sessionId;
20
+ constructor(session) {
21
+ this.sessionId = session;
22
+ this.client = BaseClient.getBaseClient(session, () => {
23
+ this.#clean();
24
+ location.reload();
25
+ });
26
+ }
27
+ async logout(sessionId) {
28
+ if (sessionId === null || sessionId === undefined) {
29
+ this.#clean();
30
+ }
31
+ await this.client.auth.logout.mutate({
32
+ sessionId: sessionId ?? null,
33
+ });
34
+ }
35
+ async me() {
36
+ return await this.client.user.self.query({});
37
+ }
38
+ static async getUser(userId, sessionId) {
39
+ const user = await getPublicUser(this.getBaseClient(sessionId), userId);
40
+ return user;
41
+ }
42
+ async getUser(userId) {
43
+ const user = await getPublicUser(this.client, userId);
44
+ return user;
45
+ }
46
+ async searchUsers(search) {
47
+ const users = await this.client.user.searchMany.query({
48
+ search,
49
+ });
50
+ return users;
51
+ }
52
+ async updateProfile(data) {
53
+ const updateProfile = await this.client.user.updateProfile.mutate(data);
54
+ return updateProfile; // TODO
55
+ }
56
+ static async isCryptoTransactionDone({ idOrHash, network = 'mainnet', }) {
57
+ const { isDone } = await this.getBaseClient().crypto.isTransactionDone.query({
58
+ idOrHash,
59
+ network,
60
+ });
61
+ return isDone;
62
+ }
63
+ async reportUser(data) {
64
+ return await this.client.report.create.mutate(data);
65
+ }
66
+ async getSponsorshipLink({ backUrl, }) {
67
+ const me = await this.me();
68
+ return getLink({
69
+ app: 'auth',
70
+ path: `/sign-up?gf=${btoa(me.account.emails[0])}&au=${btoa(backUrl)}`,
71
+ });
72
+ }
73
+ static getPaymentRequest = async ({ paymentRequestId, secrecyIdSeller, }) => {
74
+ const getPaymentRequestToPay = await BaseClient.getBaseClient().stripe.paymentRequestToPay.query({
75
+ paymentRequestId,
76
+ sellerId: secrecyIdSeller,
77
+ });
78
+ return getPaymentRequestToPay;
79
+ };
80
+ #clean = () => {
81
+ const local = getStorage(false);
82
+ const session = getStorage(true);
83
+ local.jwt.clear();
84
+ local.userAppKeys.clear();
85
+ local.userAppSession.clear();
86
+ session.jwt.clear();
87
+ session.userAppKeys.clear();
88
+ session.userAppSession.clear();
89
+ usersCache.clear();
90
+ };
91
+ }
@@ -0,0 +1,4 @@
1
+ export const filesCache = new Map();
2
+ export const nodesCache = new Map();
3
+ export const usersCache = new Map();
4
+ export const publicKeysCache = new Map();
@@ -0,0 +1,19 @@
1
+ export class SecrecyCareClient {
2
+ #client;
3
+ #keys;
4
+ #apiClient;
5
+ constructor(client, keys, apiClient) {
6
+ this.#client = client;
7
+ this.#keys = keys;
8
+ this.#apiClient = apiClient;
9
+ }
10
+ async getProfessional(input) {
11
+ return await this.#apiClient.care.professional.get.query(input);
12
+ }
13
+ async listProfessionals(input) {
14
+ return await this.#apiClient.care.professional.list.query(input);
15
+ }
16
+ async registerProfessional(input) {
17
+ return await this.#apiClient.care.professional.register.mutate(input);
18
+ }
19
+ }
@@ -0,0 +1,87 @@
1
+ import { decode } from 'jsonwebtoken';
2
+ import { getStorage } from './storage.js';
3
+ import { publicKeysCache } from '../cache.js';
4
+ export class SecrecyAppClient {
5
+ jwt;
6
+ jwtDecoded;
7
+ // #client: SecrecyClient
8
+ // #keys: KeyPair;
9
+ #apiClient;
10
+ constructor(uaJwt, _client, _keys, apiClient) {
11
+ this.jwt = uaJwt;
12
+ this.jwtDecoded = decode(uaJwt);
13
+ // this.#client = client
14
+ // this.#keys = keys;
15
+ this.#apiClient = apiClient;
16
+ }
17
+ get userId() {
18
+ return this.jwtDecoded.sub ?? '';
19
+ }
20
+ async getJwt() {
21
+ // TODO useful?
22
+ // if (this.jwtDecoded.exp && this.jwtDecoded.exp * 1000 < Date.now()) {
23
+ // return this.jwt;
24
+ // }
25
+ const { jwt } = await this.#apiClient.auth.jwt.query({
26
+ includeEmail: typeof this.jwtDecoded.email === 'string',
27
+ });
28
+ this.jwt = jwt;
29
+ this.jwtDecoded = decode(jwt);
30
+ const sessionStorage = getStorage(true);
31
+ const localStorage = getStorage(false);
32
+ const sessionJwt = sessionStorage.jwt.load();
33
+ const localJwt = localStorage.jwt.load();
34
+ if (sessionJwt !== null) {
35
+ sessionStorage.jwt.save(jwt);
36
+ }
37
+ if (localJwt !== null) {
38
+ localStorage.jwt.save(jwt);
39
+ }
40
+ return jwt;
41
+ }
42
+ async limits() {
43
+ return await this.#apiClient.application.limits.query({});
44
+ }
45
+ async metrics() {
46
+ return await this.#apiClient.application.metrics.query({});
47
+ }
48
+ async quotas() {
49
+ return await this.#apiClient.application.quotas.query({});
50
+ }
51
+ async updateNotifications(notifications) {
52
+ const updateAppNotifications = await this.#apiClient.application.updateNotification.mutate({
53
+ cloud: notifications.cloud,
54
+ disableAllUntil: notifications.disableAllUntil,
55
+ enableAll: notifications.enableAll,
56
+ mail: notifications.mail,
57
+ });
58
+ return updateAppNotifications;
59
+ }
60
+ async notifications() {
61
+ return await this.#apiClient.application.notification.query();
62
+ }
63
+ async userPublicKey(userId) {
64
+ const key = `userPublicKey:${userId}-${this.jwtDecoded.aud?.toString()}`;
65
+ const cachedPublicKey = publicKeysCache.get(key);
66
+ if (cachedPublicKey !== undefined) {
67
+ return cachedPublicKey;
68
+ }
69
+ const { publicKey } = await this.#apiClient.application.userPublicKey.query({
70
+ userId,
71
+ });
72
+ publicKeysCache.set(key, publicKey);
73
+ return publicKey;
74
+ }
75
+ async settings() {
76
+ const settings = await this.#apiClient.application.settings.query({});
77
+ return settings;
78
+ }
79
+ async updateSettings(settings) {
80
+ const updateAppSettings = await this.#apiClient.application.updateSettings.mutate({
81
+ cloudNodeDaysForDelete: settings.cloudNodeDaysForDelete,
82
+ historyFileDaysForDelete: settings.historyFileDaysForDelete,
83
+ historyMaxFileCount: settings.historyMaxFileCount,
84
+ });
85
+ return updateAppSettings;
86
+ }
87
+ }
@@ -0,0 +1,475 @@
1
+ import axios from 'axios';
2
+ import ky from 'ky';
3
+ import { encryptName } from '../index.js';
4
+ import { nodesCache, filesCache } from '../cache.js';
5
+ import { secretstreamKeygen } from '../crypto/file.js';
6
+ import { decryptCryptoBox, encryptCryptoBox } from '../crypto/index.js';
7
+ import { compress, decompress } from '../minify/index.js';
8
+ import { sodium } from '../sodium.js';
9
+ import { enumerate, chunks, concatenate } from '../utils/array.js';
10
+ import { md5 } from '../worker/md5.js';
11
+ import { decrypt, encrypt } from '../worker/sodium.js';
12
+ import { apiFileToExternal } from './convert/file.js';
13
+ import { apiNodeFullToInternalFull, apiNodeToExternal, apiNodeToExternalNodeFull, internalNodeFullToNodeFull, } from './convert/node.js';
14
+ import { promiseAllLimit } from '../utils/promise.js';
15
+ // import { md5 } from "../worker/index.js";
16
+ // import { firstValueFrom, of } from "rxjs";
17
+ export class SecrecyCloudClient {
18
+ #client;
19
+ #keys;
20
+ #apiClient;
21
+ constructor(client, keys, apiClient) {
22
+ this.#client = client;
23
+ this.#keys = keys;
24
+ this.#apiClient = apiClient;
25
+ }
26
+ async addFileToHistory({ fileId, nodeId, }) {
27
+ const addFileToHistory = await this.#apiClient.cloud.addFileToHistory.mutate({
28
+ fileId,
29
+ nodeId,
30
+ });
31
+ const node = await apiNodeFullToInternalFull(addFileToHistory, this.#keys);
32
+ const file = node.history.find((f) => f.id === fileId);
33
+ if (file !== undefined) {
34
+ const users = node.users.filter(([u]) => u.id !== this.#client.app.userId);
35
+ await this.#apiClient.cloud.shareFileInHistory.mutate({
36
+ fileId: file.id,
37
+ nodeId,
38
+ users: users.map(([u]) => ({
39
+ id: u.id,
40
+ key: sodium.to_hex(encryptCryptoBox(sodium.from_hex(file.key), this.#keys.publicKey, this.#keys.privateKey)),
41
+ })),
42
+ });
43
+ }
44
+ return internalNodeFullToNodeFull(node);
45
+ }
46
+ async uploadFile({ file, encryptProgress, uploadProgress, signal, }) {
47
+ const fileKey = secretstreamKeygen();
48
+ const fileBuffer = file instanceof File ? new Uint8Array(await file.arrayBuffer()) : file;
49
+ const compressed = compress(fileBuffer);
50
+ const { data: encryptedFile, md5: md5File, md5Encrypted, } = await encrypt(fileKey, compressed, encryptProgress, signal);
51
+ const encryptedFileKey = encryptCryptoBox(fileKey, this.#keys.publicKey, this.#keys.privateKey);
52
+ const uploadFile = await this.#apiClient.cloud.uploadFile.mutate({
53
+ fileSize: BigInt(encryptedFile.byteLength),
54
+ fileSizeBefore: BigInt(fileBuffer.byteLength),
55
+ fileKey: sodium.to_hex(encryptedFileKey),
56
+ md5Encrypted,
57
+ md5: md5File,
58
+ });
59
+ await uploadProgress?.({
60
+ total: encryptedFile.byteLength,
61
+ current: 0,
62
+ percent: 0,
63
+ });
64
+ if (uploadFile.parts.length === 0) {
65
+ await uploadProgress?.({
66
+ total: encryptedFile.byteLength,
67
+ current: encryptedFile.byteLength,
68
+ percent: 1,
69
+ });
70
+ return uploadFile.fileId;
71
+ }
72
+ const uploadPartEnded = async (md5, order) => {
73
+ const { isUploadPartEnded } = await this.#apiClient.cloud.uploadFilePartEnd.mutate({
74
+ fileId: uploadFile.fileId,
75
+ md5,
76
+ order,
77
+ });
78
+ return isUploadPartEnded;
79
+ };
80
+ const uploadEnded = async () => {
81
+ const { isUploadEnded } = await this.#apiClient.cloud.uploadFileEnd.mutate({
82
+ fileId: uploadFile.fileId,
83
+ });
84
+ return isUploadEnded;
85
+ };
86
+ const chunkParts = new Array();
87
+ for (const [index, chunk] of enumerate(chunks(encryptedFile, Number(uploadFile.filePartSize)))) {
88
+ chunkParts.push({
89
+ order: index + 1,
90
+ data: chunk,
91
+ md5: await md5(chunk),
92
+ });
93
+ }
94
+ const progressParts = {};
95
+ const onProgress = (part, progressEvent) => {
96
+ progressParts[part] = progressEvent;
97
+ const current = Object.values(progressParts).reduce((prv, cur) => prv + cur.loaded, 0);
98
+ void uploadProgress?.({
99
+ percent: current / encryptedFile.byteLength,
100
+ total: encryptedFile.byteLength,
101
+ current,
102
+ });
103
+ };
104
+ const byPart = async (part) => {
105
+ const formData = new FormData();
106
+ const chunk = chunkParts.find((p) => p.order === part.order);
107
+ if (chunk === undefined) {
108
+ return;
109
+ }
110
+ for (const [key, value] of Object.entries(part.fields)) {
111
+ formData.append(key, value);
112
+ }
113
+ formData.append('file', new Blob([chunk.data]), `${uploadFile.fileId}-${chunk.order}`);
114
+ await axios.post(part.url, formData, {
115
+ onUploadProgress: (progressEvent) => {
116
+ onProgress(part.order, progressEvent);
117
+ },
118
+ signal,
119
+ });
120
+ await uploadPartEnded(chunk.md5, chunk.order);
121
+ // if ((e as any).response.status === 0) {
122
+ // // TODO https://github.com/sindresorhus/ky/issues/305
123
+ // } else {
124
+ // throw e;
125
+ // }
126
+ };
127
+ await promiseAllLimit(3, uploadFile.parts.map((p) => async () => {
128
+ await byPart(p);
129
+ }));
130
+ await uploadEnded();
131
+ return uploadFile.fileId;
132
+ }
133
+ async uploadFileInCloud({ file, name, nodeId, encryptProgress, uploadProgress, signal, }) {
134
+ const fileId = await this.uploadFile({
135
+ file,
136
+ encryptProgress,
137
+ uploadProgress,
138
+ signal,
139
+ });
140
+ return await this.saveInCloud({
141
+ fileId,
142
+ name,
143
+ nodeId,
144
+ });
145
+ }
146
+ async deletedNodes() {
147
+ const deletedNodes = await this.#apiClient.cloud.nodesDeleted.query({});
148
+ return await Promise.all(deletedNodes.map(async (node) => await apiNodeToExternal(node, this.#keys)));
149
+ }
150
+ async sharedNodes() {
151
+ const nodesShared = await this.#apiClient.cloud.nodesShared.query();
152
+ return await Promise.all(nodesShared.map(async (node) => await apiNodeToExternal(node, this.#keys)));
153
+ }
154
+ async nodesSharedWithMe(type = 'FOLDER') {
155
+ const nodesSharedWithMe = await this.#apiClient.cloud.nodesSharedWithMe.query({
156
+ type,
157
+ });
158
+ return await Promise.all(nodesSharedWithMe.map(async (node) => await apiNodeToExternal(node, this.#keys)));
159
+ }
160
+ async deleteNodeSharing({ nodeId, userId, }) {
161
+ const { isDeleted } = await this.#apiClient.cloud.deleteNodeSharing.mutate({
162
+ nodeId,
163
+ userId,
164
+ });
165
+ return isDeleted;
166
+ }
167
+ async duplicateNode({ nodeId, folderId, customName, }) {
168
+ let node = nodesCache.get(nodeId);
169
+ if (node === undefined) {
170
+ await this.node({ id: nodeId });
171
+ node = nodesCache.get(nodeId);
172
+ if (node === undefined) {
173
+ throw new Error(`Node (${nodeId}) does not exists`);
174
+ }
175
+ }
176
+ if (node.access?.nameKey === undefined) {
177
+ throw new Error(`Can't have access to node ${nodeId}`);
178
+ }
179
+ customName =
180
+ typeof customName === 'string' && node.access.nameKey !== null
181
+ ? await encryptName(customName, node.access.nameKey)
182
+ : null;
183
+ const { isDuplicated } = await this.#apiClient.cloud.duplicateNode.mutate({
184
+ nodeId,
185
+ folderId: folderId ?? null,
186
+ name: customName,
187
+ });
188
+ return isDuplicated;
189
+ }
190
+ async deleteNodeCloudTrash({ ids }) {
191
+ const { isDeleted } = await this.#apiClient.cloud.deleteNodeCloudTrash.mutate({
192
+ ids,
193
+ });
194
+ return isDeleted;
195
+ }
196
+ async createFolder({ name, parentFolderId, }) {
197
+ const key = secretstreamKeygen();
198
+ const encryptedName = await encryptName(name, sodium.to_hex(key));
199
+ const encryptedKey = encryptCryptoBox(key, this.#keys.publicKey, this.#keys.privateKey);
200
+ const createFolder = await this.#apiClient.cloud.createFolder.mutate({
201
+ name: encryptedName,
202
+ parentId: parentFolderId ?? null,
203
+ nameKey: sodium.to_hex(encryptedKey),
204
+ });
205
+ const folder = await apiNodeToExternalNodeFull(createFolder, this.#keys);
206
+ const users = folder.parent?.users?.filter(([u]) => u.id !== this.#client.app.userId) ??
207
+ [];
208
+ if (users.length > 0) {
209
+ await Promise.all(users.map(async ([u, rights]) => await this.shareNode({
210
+ nodeId: folder.id,
211
+ rights,
212
+ userId: u.id,
213
+ })));
214
+ }
215
+ return folder;
216
+ }
217
+ async node({ id, deleted, } = {}) {
218
+ const node = await this.#apiClient.cloud.nodeFullById.query({
219
+ id,
220
+ deleted,
221
+ });
222
+ return await apiNodeToExternalNodeFull(node, this.#keys);
223
+ }
224
+ async fileMetadata({ id }) {
225
+ const file = await this.#apiClient.cloud.fileById.query({
226
+ id,
227
+ });
228
+ return apiFileToExternal(file, this.#keys);
229
+ }
230
+ async shareNode({ nodeId, userId, rights, }) {
231
+ const publicKey = await this.#client.app.userPublicKey(userId);
232
+ const { ids } = await this.#apiClient.cloud.shareNode.mutate({
233
+ nodeId,
234
+ userId,
235
+ });
236
+ const shareNodes = [];
237
+ for (const id of ids) {
238
+ const nameKey = await this.perNode(id, publicKey);
239
+ if (nameKey !== null) {
240
+ shareNodes.push(nameKey);
241
+ }
242
+ }
243
+ const { isFinished } = await this.#apiClient.cloud.shareNodeFinish.mutate({
244
+ rights,
245
+ userId,
246
+ nodes: shareNodes,
247
+ });
248
+ return isFinished;
249
+ }
250
+ async updateNode({ nodeId, name, isFavorite, deletedAt, }) {
251
+ let node = nodesCache.get(nodeId);
252
+ if (node === undefined) {
253
+ await this.node({ id: nodeId });
254
+ node = nodesCache.get(nodeId);
255
+ if (node === undefined) {
256
+ throw `Can't find Node ${nodeId}`;
257
+ }
258
+ }
259
+ if (node.access?.nameKey === undefined) {
260
+ throw new Error(`Can't have access to node ${nodeId}`);
261
+ }
262
+ name =
263
+ typeof name === 'string' && node.access.nameKey !== null
264
+ ? await encryptName(name, node.access.nameKey)
265
+ : null;
266
+ const updateNode = await this.#apiClient.cloud.updateNode.mutate({
267
+ deletedAt: deletedAt ?? null,
268
+ id: nodeId,
269
+ isFavorite: isFavorite ?? null,
270
+ name,
271
+ });
272
+ return await apiNodeToExternalNodeFull(updateNode, this.#keys);
273
+ }
274
+ async fileContent({ fileId, onDownloadProgress, progressDecrypt, signal, }) {
275
+ const fileContent = await this.#apiClient.cloud.fileContentById.query({
276
+ id: fileId,
277
+ });
278
+ const progressParts = {};
279
+ const onProgress = (part, progressEvent) => {
280
+ progressParts[part] = progressEvent;
281
+ const transferredBytes = Object.values(progressParts).reduce((prv, cur) => prv + cur.transferredBytes, 0);
282
+ const totalBytes = Number(fileContent.totalSize);
283
+ onDownloadProgress?.({
284
+ percent: transferredBytes / totalBytes,
285
+ totalBytes,
286
+ transferredBytes,
287
+ });
288
+ };
289
+ const encryptedContentFromParts = async (fileParts) => {
290
+ const parts = new Array();
291
+ const byPart = async (part) => {
292
+ const buf = new Uint8Array(await ky
293
+ .get(part.contentUrl, {
294
+ timeout: false,
295
+ onDownloadProgress: (pr) => {
296
+ onProgress(part.order, pr);
297
+ },
298
+ signal,
299
+ })
300
+ .arrayBuffer());
301
+ const md5Part = await md5(buf);
302
+ if (md5Part !== part.md5) {
303
+ throw new Error(`Invalid md5 for part ${part.order} of file ${fileId}`);
304
+ }
305
+ parts.push({
306
+ data: buf,
307
+ order: part.order,
308
+ });
309
+ };
310
+ await promiseAllLimit(3, fileParts.map((p) => async () => {
311
+ await byPart(p);
312
+ }));
313
+ return concatenate(...parts.sort((a, b) => a.order - b.order).map((p) => p.data));
314
+ };
315
+ const finalize = async (encryptedContent) => {
316
+ // const md5Encrypted = await firstValueFrom(md5(of(encryptedContent)));
317
+ const md5Encrypted = await md5(encryptedContent);
318
+ if (md5Encrypted !== fileContent.md5Encrypted) {
319
+ throw new Error(`Encrypted content does not match`);
320
+ }
321
+ const key = decryptCryptoBox(sodium.from_hex(fileContent.key), fileContent.type === 'received_mail'
322
+ ? fileContent.senderPublicKey
323
+ : fileContent.type === 'cloud'
324
+ ? fileContent.publicKey
325
+ : this.#keys.publicKey, this.#keys.privateKey);
326
+ const src = await decrypt(key, encryptedContent, progressDecrypt, signal);
327
+ // const md5Content = await firstValueFrom(md5(of(src)));
328
+ const md5Content = await md5(src);
329
+ if (md5Content !== fileContent.md5) {
330
+ throw new Error(`Content does not match`);
331
+ }
332
+ return decompress(src);
333
+ };
334
+ const encryptedContent = fileContent.type === 'lite'
335
+ ? fileContent.content
336
+ : fileContent.type === 'cloud'
337
+ ? await encryptedContentFromParts(fileContent.parts)
338
+ : // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
339
+ fileContent.maybeContent !== null
340
+ ? fileContent.maybeContent
341
+ : fileContent.maybeParts !== null
342
+ ? await encryptedContentFromParts(fileContent.maybeParts)
343
+ : null;
344
+ if (encryptedContent === null) {
345
+ throw `Can't find content for file ${fileId}`;
346
+ }
347
+ return await finalize(encryptedContent);
348
+ }
349
+ async deleteFile({ fileId, nodeId, }) {
350
+ const { isDeleted } = await this.#apiClient.cloud.deleteFile.mutate({
351
+ fileId,
352
+ nodeId,
353
+ });
354
+ return isDeleted;
355
+ }
356
+ async deleteNode({ nodeId }) {
357
+ const { isDeleted } = await this.#apiClient.cloud.deleteNode.mutate({
358
+ id: nodeId,
359
+ });
360
+ return isDeleted;
361
+ }
362
+ async emptyTrash() {
363
+ const { isCleaned } = await this.#apiClient.cloud.emptyNodeCloudTrash.mutate({});
364
+ return isCleaned;
365
+ }
366
+ async recoverNode(id) {
367
+ const { isRecovered } = await this.#apiClient.cloud.recoverNode.mutate({
368
+ id,
369
+ });
370
+ return isRecovered;
371
+ }
372
+ async moveNodes({ nodeIds, parentNodeId, }) {
373
+ const { isMoved } = await this.#apiClient.cloud.moveNodes.mutate({
374
+ ids: nodeIds,
375
+ parentId: parentNodeId ?? null,
376
+ });
377
+ return isMoved;
378
+ }
379
+ async saveInCloud({ fileId, name, nodeId, }) {
380
+ if (nodeId !== undefined && !nodesCache.has(nodeId)) {
381
+ await this.node({ id: nodeId });
382
+ if (!nodesCache.has(nodeId)) {
383
+ const err = {
384
+ name: 'ClientError',
385
+ code: 'NOT_FOUND',
386
+ message: `The node ${nodeId} does not exists`,
387
+ };
388
+ throw err;
389
+ }
390
+ }
391
+ let key = '';
392
+ const file = filesCache.get(fileId);
393
+ if (file === undefined) {
394
+ await this.fileMetadata({ id: fileId });
395
+ const file = filesCache.get(fileId);
396
+ if (file === undefined) {
397
+ const receivedMails = await this.#client.mail.receivedMails();
398
+ const mail = receivedMails.find((m) => m.files.some((f) => f.id === fileId));
399
+ if (mail === undefined) {
400
+ const err = {
401
+ name: 'ClientError',
402
+ code: 'NOT_FOUND',
403
+ message: `Can't find mail with the file ${fileId}`,
404
+ };
405
+ throw err;
406
+ }
407
+ const fileMail = mail.files.find((f) => f.id === fileId);
408
+ if (fileMail === undefined) {
409
+ const err = {
410
+ name: 'ClientError',
411
+ code: 'NOT_FOUND',
412
+ message: `Can't find mail with the file ${fileId}`,
413
+ };
414
+ throw err;
415
+ }
416
+ const senderPubKey = await this.#client.app.userPublicKey(mail.sender.id);
417
+ const fileKey = decryptCryptoBox(sodium.from_hex(fileMail.key), senderPubKey, this.#keys.privateKey);
418
+ key = sodium.to_hex(fileKey);
419
+ }
420
+ else {
421
+ key = file.key;
422
+ }
423
+ }
424
+ else {
425
+ key = file.key;
426
+ }
427
+ key = sodium.to_hex(encryptCryptoBox(sodium.from_hex(key), this.#keys.publicKey, this.#keys.privateKey));
428
+ const nameKey = secretstreamKeygen();
429
+ const encryptedName = await encryptName(name, sodium.to_hex(nameKey));
430
+ const encryptedNameKey = sodium.to_hex(encryptCryptoBox(nameKey, this.#keys.publicKey, this.#keys.privateKey));
431
+ const saveInCloud = await this.#apiClient.cloud.saveInCloud.mutate({
432
+ fileId,
433
+ key,
434
+ nodeId: nodeId ?? null,
435
+ filename: encryptedName,
436
+ nameKey: encryptedNameKey,
437
+ });
438
+ const node = await apiNodeToExternalNodeFull(saveInCloud, this.#keys);
439
+ const me = node.parent?.users.find(([u]) => u.id === this.#client.app.userId);
440
+ if (me !== undefined && ['admin', 'write'].includes(me[1])) {
441
+ const others = node.parent?.users.filter(([u]) => u.id !== this.#client.app.userId) ??
442
+ [];
443
+ await Promise.all(others.map(async ([u, rights]) => await this.shareNode({
444
+ nodeId: node.id,
445
+ rights,
446
+ userId: u.id,
447
+ })));
448
+ }
449
+ return node;
450
+ }
451
+ perNode = async (nodeId, publicKey) => {
452
+ let node = nodesCache.get(nodeId);
453
+ if (node === undefined) {
454
+ await this.node({ id: nodeId });
455
+ node = nodesCache.get(nodeId);
456
+ if (node === undefined) {
457
+ return null;
458
+ }
459
+ }
460
+ const nameKey = node.access?.nameKey;
461
+ if (nameKey === null) {
462
+ return null;
463
+ }
464
+ return {
465
+ id: node.id,
466
+ nameKey: sodium.to_hex(encryptCryptoBox(sodium.from_hex(nameKey), publicKey, this.#keys.privateKey)),
467
+ files: 'history' in node
468
+ ? node.history.map((f) => ({
469
+ id: f.id,
470
+ key: sodium.to_hex(encryptCryptoBox(sodium.from_hex(f.key), publicKey, this.#keys.privateKey)),
471
+ }))
472
+ : [],
473
+ };
474
+ };
475
+ }