@secrecy/lib 1.83.0 → 1.83.2-fix-upload-data-error-handling.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.
@@ -179,19 +179,24 @@ export class SecrecyCloudClient {
179
179
  deleted,
180
180
  fromIdentityPubKey: this.#client.currentIdentity.identityPubKey,
181
181
  });
182
- const errors = apiNodes.missingIds.map((id) => `Node ${id} not found`);
183
- const decryptedNodes = await Promise.allSettled(apiNodes.nodes.map((node) => apiNodeToExternal(node, this.#client.keyPairs)));
184
- const nodes = decryptedNodes
185
- .filter((result) => result.status === 'fulfilled')
186
- .map((result) => result.value);
187
- const errorNodes = decryptedNodes
188
- .filter((result) => result.status === 'rejected')
189
- .map((result) => result.reason);
190
- errors.push(...errorNodes.map((err) => typeof err === 'string'
191
- ? err
192
- : err instanceof Error
193
- ? err.message
194
- : JSON.stringify(err)));
182
+ const errors = apiNodes.missingIds.map((id) => ({
183
+ message: `Node ${id} not found`,
184
+ nodeId: id,
185
+ }));
186
+ const nodes = [];
187
+ const handleNode = async (node) => {
188
+ try {
189
+ const externalNode = await apiNodeToExternal(node, this.#client.keyPairs);
190
+ nodes.push(externalNode);
191
+ }
192
+ catch (error) {
193
+ errors.push({
194
+ message: error instanceof Error ? error.message : String(error),
195
+ nodeId: node.id,
196
+ });
197
+ }
198
+ };
199
+ await Promise.all(apiNodes.nodes.map(handleNode));
195
200
  return { nodes, errors };
196
201
  }
197
202
  async nodesFull({ ids, deleted, }) {
@@ -200,19 +205,24 @@ export class SecrecyCloudClient {
200
205
  deleted,
201
206
  fromIdentityPubKey: this.#client.currentIdentity.identityPubKey,
202
207
  });
203
- const errors = apiNodes.missingIds.map((id) => `Node ${id} not found`);
204
- const decryptedNodes = await Promise.allSettled(apiNodes.nodes.map((node) => apiNodeToExternalNodeFull(node, this.#client.keyPairs)));
205
- const nodes = decryptedNodes
206
- .filter((result) => result.status === 'fulfilled')
207
- .map((result) => result.value);
208
- const errorNodes = decryptedNodes
209
- .filter((result) => result.status === 'rejected')
210
- .map((result) => result.reason);
211
- errors.push(...errorNodes.map((err) => typeof err === 'string'
212
- ? err
213
- : err instanceof Error
214
- ? err.message
215
- : JSON.stringify(err)));
208
+ const errors = apiNodes.missingIds.map((id) => ({
209
+ message: `Node ${id} not found`,
210
+ nodeId: id,
211
+ }));
212
+ const nodes = [];
213
+ const handleNode = async (node) => {
214
+ try {
215
+ const externalNode = await apiNodeToExternalNodeFull(node, this.#client.keyPairs);
216
+ nodes.push(externalNode);
217
+ }
218
+ catch (error) {
219
+ errors.push({
220
+ message: error instanceof Error ? error.message : String(error),
221
+ nodeId: node.id,
222
+ });
223
+ }
224
+ };
225
+ await Promise.all(apiNodes.nodes.map(handleNode));
216
226
  return { nodes, errors };
217
227
  }
218
228
  async dataMetadata({ id }) {
@@ -7,7 +7,6 @@ import { dataContentCache } from '../cache.js';
7
7
  import { chunks, enumerate } from '../utils/array.js';
8
8
  import { getTrpcGuestClient, } from '../client.js';
9
9
  import axios from 'axios';
10
- import { promiseAllLimit } from '../utils/promise.js';
11
10
  import { encryptDataAndKey } from '../crypto/domain.js';
12
11
  import { derivePassword, generatePassword } from '../crypto/helpers.js';
13
12
  import { decryptCryptoBox, encryptSecretBox } from '../crypto/index.js';
@@ -213,13 +212,13 @@ export async function uploadData({ storageType, data, password, forcePassword =
213
212
  const formData = new FormData();
214
213
  const chunk = chunkParts.find((p) => p.order === part.order);
215
214
  if (chunk === undefined) {
216
- return;
215
+ throw new Error(`Unable to find chunk data for part ${part.order} of data ${uploadData.id}`);
217
216
  }
218
217
  for (const [key, value] of Object.entries(part.fields)) {
219
218
  formData.append(key, value);
220
219
  }
221
220
  formData.append('file', new Blob([chunk.data], { type: filetype?.mime }), `${uploadData.id}-${chunk.order}`);
222
- await axios.post(part.url, formData, {
221
+ const response = await axios.post(part.url, formData, {
223
222
  onUploadProgress: (progressEvent) => {
224
223
  onProgress(part.order, {
225
224
  percent: progressEvent.progress ?? 0,
@@ -227,12 +226,33 @@ export async function uploadData({ storageType, data, password, forcePassword =
227
226
  transferredBytes: progressEvent.loaded,
228
227
  });
229
228
  },
229
+ signal,
230
230
  });
231
+ // TODO implement retry etc
232
+ if (response.status !== 204 && response.status !== 201) {
233
+ throw new Error(`Failed to upload data part ${part.order} of data ${uploadData.id}`);
234
+ }
231
235
  return uploadDataPartEnd(chunk.md5, chunk.order);
232
236
  };
233
- await promiseAllLimit(3, uploadData.parts.map((p) => async () => {
234
- await byPart(p);
235
- }));
237
+ let lastValidatedCalled = false;
238
+ for (const part of uploadData.parts) {
239
+ const res = await byPart(part);
240
+ if (!res.isUploadPartEnded) {
241
+ throw new Error(`Failed to confirm upload of part ${part.order} of data ${uploadData.id}`);
242
+ }
243
+ if (res.isLastValidatedPart) {
244
+ lastValidatedCalled = true;
245
+ await uploadProgress?.({
246
+ total: encryptedData.byteLength,
247
+ current: encryptedData.byteLength,
248
+ percent: 1,
249
+ });
250
+ break;
251
+ }
252
+ }
253
+ if (!lastValidatedCalled) {
254
+ throw new Error(`Failed to validate the last part of data ${uploadData.id}`);
255
+ }
236
256
  const sharing = createSharing({ data: uploadData });
237
257
  const localData = {
238
258
  id: uploadData.id,
@@ -3,7 +3,6 @@ import { secretStreamKeygen } from './data';
3
3
  import { encrypt } from '../worker/sodium';
4
4
  import ky from 'ky';
5
5
  import { md5 } from '../worker/md5.js';
6
- import { promiseAllLimit } from '../utils/promise.js';
7
6
  import { concatenate } from '../utils/array.js';
8
7
  /**
9
8
  * Encrypt the dataKey and the data as logged or guest user.
@@ -53,7 +52,9 @@ const encryptedContentFromParts = async (arg) => {
53
52
  parts[arg.dataId] ??= [];
54
53
  parts[arg.dataId].push({ data: buf, order: part.order });
55
54
  };
56
- await promiseAllLimit(3, arg.dataParts.map((p) => async () => byPart(p)));
55
+ for (const part of arg.dataParts) {
56
+ await byPart(part);
57
+ }
57
58
  return concatenate(...parts[arg.dataId].sort((a, b) => a.order - b.order).map((p) => p.data));
58
59
  };
59
60
  export async function buildBytesFromApiData({ dataContent, totalBytes, progressParts, onDownloadProgress, signal, }) {
@@ -61,14 +61,20 @@ export declare class SecrecyCloudClient {
61
61
  deleted?: boolean | null | undefined;
62
62
  }): Promise<{
63
63
  nodes: Node[];
64
- errors: string[];
64
+ errors: {
65
+ message: string;
66
+ nodeId: string;
67
+ }[];
65
68
  }>;
66
69
  nodesFull({ ids, deleted, }: {
67
70
  ids: string[];
68
71
  deleted?: boolean | null | undefined;
69
72
  }): Promise<{
70
73
  nodes: NodeFull[];
71
- errors: string[];
74
+ errors: {
75
+ message: string;
76
+ nodeId: string;
77
+ }[];
72
78
  }>;
73
79
  dataMetadata({ id }: {
74
80
  id: string;
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.83.0",
5
+ "version": "1.83.2-fix-upload-data-error-handling.1",
6
6
  "repository": {
7
7
  "type": "git",
8
8
  "url": "https://github.com/anonymize-org/lib.git"
@@ -1,20 +0,0 @@
1
- export const promiseAllLimit = async (n, list) => {
2
- const head = list.slice(0, n);
3
- const tail = list.slice(n);
4
- const result = [];
5
- const execute = async (promise, i, runNext) => {
6
- result[i] = await promise();
7
- await runNext();
8
- };
9
- const runNext = async () => {
10
- const i = list.length - tail.length;
11
- const promise = tail.shift();
12
- if (promise !== undefined) {
13
- await execute(promise, i, runNext);
14
- }
15
- };
16
- await Promise.all(head.map(async (promise, i) => {
17
- await execute(promise, i, runNext);
18
- }));
19
- return result;
20
- };
@@ -1 +0,0 @@
1
- export declare const promiseAllLimit: <T>(n: number, list: Array<() => Promise<T>>) => Promise<T[]>;