@protontech/drive-sdk 0.9.9 → 0.11.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 (213) hide show
  1. package/dist/crypto/driveCrypto.d.ts +19 -16
  2. package/dist/crypto/driveCrypto.js +23 -1
  3. package/dist/crypto/driveCrypto.js.map +1 -1
  4. package/dist/crypto/driveCrypto.test.js +2 -1
  5. package/dist/crypto/driveCrypto.test.js.map +1 -1
  6. package/dist/crypto/hmac.d.ts +3 -3
  7. package/dist/crypto/hmac.js.map +1 -1
  8. package/dist/crypto/interface.d.ts +45 -25
  9. package/dist/crypto/interface.js.map +1 -1
  10. package/dist/crypto/openPGPCrypto.d.ts +37 -37
  11. package/dist/crypto/openPGPCrypto.js.map +1 -1
  12. package/dist/crypto/utils.d.ts +1 -1
  13. package/dist/diagnostic/telemetry.js +3 -0
  14. package/dist/diagnostic/telemetry.js.map +1 -1
  15. package/dist/interface/index.d.ts +4 -3
  16. package/dist/interface/index.js +3 -1
  17. package/dist/interface/index.js.map +1 -1
  18. package/dist/interface/nodes.d.ts +8 -0
  19. package/dist/interface/photos.d.ts +31 -2
  20. package/dist/interface/photos.js +14 -0
  21. package/dist/interface/photos.js.map +1 -1
  22. package/dist/interface/sharing.d.ts +2 -0
  23. package/dist/interface/telemetry.d.ts +13 -1
  24. package/dist/interface/telemetry.js.map +1 -1
  25. package/dist/interface/thumbnail.d.ts +2 -2
  26. package/dist/internal/apiService/apiService.d.ts +1 -1
  27. package/dist/internal/apiService/apiService.js +27 -14
  28. package/dist/internal/apiService/apiService.js.map +1 -1
  29. package/dist/internal/apiService/apiService.test.js +33 -5
  30. package/dist/internal/apiService/apiService.test.js.map +1 -1
  31. package/dist/internal/apiService/driveTypes.d.ts +2942 -3187
  32. package/dist/internal/apiService/errors.test.js +17 -7
  33. package/dist/internal/apiService/errors.test.js.map +1 -1
  34. package/dist/internal/devices/manager.d.ts +1 -0
  35. package/dist/internal/devices/manager.js +11 -0
  36. package/dist/internal/devices/manager.js.map +1 -1
  37. package/dist/internal/download/apiService.d.ts +1 -1
  38. package/dist/internal/download/cryptoService.d.ts +4 -4
  39. package/dist/internal/download/cryptoService.js.map +1 -1
  40. package/dist/internal/download/fileDownloader.js.map +1 -1
  41. package/dist/internal/download/fileDownloader.test.js.map +1 -1
  42. package/dist/internal/download/thumbnailDownloader.js.map +1 -1
  43. package/dist/internal/nodes/apiService.js.map +1 -1
  44. package/dist/internal/nodes/cryptoService.d.ts +4 -4
  45. package/dist/internal/nodes/cryptoService.js +5 -3
  46. package/dist/internal/nodes/cryptoService.js.map +1 -1
  47. package/dist/internal/nodes/cryptoService.test.js.map +1 -1
  48. package/dist/internal/nodes/interface.d.ts +1 -1
  49. package/dist/internal/nodes/nodesAccess.js +1 -1
  50. package/dist/internal/nodes/nodesAccess.js.map +1 -1
  51. package/dist/internal/nodes/nodesManagement.js +0 -1
  52. package/dist/internal/nodes/nodesManagement.js.map +1 -1
  53. package/dist/internal/photos/addToAlbum.d.ts +42 -0
  54. package/dist/internal/photos/addToAlbum.js +178 -0
  55. package/dist/internal/photos/addToAlbum.js.map +1 -0
  56. package/dist/internal/photos/addToAlbum.test.js +409 -0
  57. package/dist/internal/photos/addToAlbum.test.js.map +1 -0
  58. package/dist/internal/photos/albumsCrypto.d.ts +20 -3
  59. package/dist/internal/photos/albumsCrypto.js +27 -0
  60. package/dist/internal/photos/albumsCrypto.js.map +1 -1
  61. package/dist/internal/photos/{albums.d.ts → albumsManager.d.ts} +6 -4
  62. package/dist/internal/photos/{albums.js → albumsManager.js} +17 -5
  63. package/dist/internal/photos/albumsManager.js.map +1 -0
  64. package/dist/internal/photos/albumsManager.test.d.ts +1 -0
  65. package/dist/internal/photos/{albums.test.js → albumsManager.test.js} +4 -3
  66. package/dist/internal/photos/albumsManager.test.js.map +1 -0
  67. package/dist/internal/photos/apiService.d.ts +22 -2
  68. package/dist/internal/photos/apiService.js +136 -5
  69. package/dist/internal/photos/apiService.js.map +1 -1
  70. package/dist/internal/photos/apiService.test.d.ts +1 -0
  71. package/dist/internal/photos/apiService.test.js +199 -0
  72. package/dist/internal/photos/apiService.test.js.map +1 -0
  73. package/dist/internal/photos/errors.d.ts +4 -0
  74. package/dist/internal/photos/errors.js +17 -0
  75. package/dist/internal/photos/errors.js.map +1 -0
  76. package/dist/internal/photos/index.d.ts +5 -3
  77. package/dist/internal/photos/index.js +5 -2
  78. package/dist/internal/photos/index.js.map +1 -1
  79. package/dist/internal/photos/interface.d.ts +6 -15
  80. package/dist/internal/photos/interface.js +0 -14
  81. package/dist/internal/photos/interface.js.map +1 -1
  82. package/dist/internal/photos/nodes.js +33 -3
  83. package/dist/internal/photos/nodes.js.map +1 -1
  84. package/dist/internal/photos/nodes.test.js +25 -5
  85. package/dist/internal/photos/nodes.test.js.map +1 -1
  86. package/dist/internal/photos/photosManager.d.ts +22 -0
  87. package/dist/internal/photos/photosManager.js +101 -0
  88. package/dist/internal/photos/photosManager.js.map +1 -0
  89. package/dist/internal/photos/photosManager.test.d.ts +1 -0
  90. package/dist/internal/photos/photosManager.test.js +222 -0
  91. package/dist/internal/photos/photosManager.test.js.map +1 -0
  92. package/dist/internal/photos/photosTransferPayloadBuilder.d.ts +57 -0
  93. package/dist/internal/photos/photosTransferPayloadBuilder.js +113 -0
  94. package/dist/internal/photos/photosTransferPayloadBuilder.js.map +1 -0
  95. package/dist/internal/photos/photosTransferPayloadBuilder.test.d.ts +1 -0
  96. package/dist/internal/photos/photosTransferPayloadBuilder.test.js +289 -0
  97. package/dist/internal/photos/photosTransferPayloadBuilder.test.js.map +1 -0
  98. package/dist/internal/photos/upload.d.ts +2 -2
  99. package/dist/internal/photos/upload.js +1 -1
  100. package/dist/internal/photos/upload.js.map +1 -1
  101. package/dist/internal/shares/apiService.js +1 -0
  102. package/dist/internal/shares/apiService.js.map +1 -1
  103. package/dist/internal/shares/interface.d.ts +1 -0
  104. package/dist/internal/sharing/apiService.d.ts +8 -1
  105. package/dist/internal/sharing/apiService.js +23 -1
  106. package/dist/internal/sharing/apiService.js.map +1 -1
  107. package/dist/internal/sharing/cryptoService.js +8 -4
  108. package/dist/internal/sharing/cryptoService.js.map +1 -1
  109. package/dist/internal/sharing/sharingManagement.d.ts +1 -0
  110. package/dist/internal/sharing/sharingManagement.js +15 -2
  111. package/dist/internal/sharing/sharingManagement.js.map +1 -1
  112. package/dist/internal/sharing/sharingManagement.test.js +30 -5
  113. package/dist/internal/sharing/sharingManagement.test.js.map +1 -1
  114. package/dist/internal/sharingPublic/nodes.d.ts +2 -2
  115. package/dist/internal/sharingPublic/nodes.js.map +1 -1
  116. package/dist/internal/upload/apiService.d.ts +3 -7
  117. package/dist/internal/upload/apiService.js +0 -4
  118. package/dist/internal/upload/apiService.js.map +1 -1
  119. package/dist/internal/upload/blockVerifier.d.ts +2 -2
  120. package/dist/internal/upload/blockVerifier.js.map +1 -1
  121. package/dist/internal/upload/chunkStreamReader.d.ts +2 -2
  122. package/dist/internal/upload/chunkStreamReader.js.map +1 -1
  123. package/dist/internal/upload/chunkStreamReader.test.js.map +1 -1
  124. package/dist/internal/upload/cryptoService.d.ts +7 -7
  125. package/dist/internal/upload/cryptoService.js +4 -4
  126. package/dist/internal/upload/cryptoService.js.map +1 -1
  127. package/dist/internal/upload/interface.d.ts +6 -6
  128. package/dist/internal/upload/manager.d.ts +1 -1
  129. package/dist/internal/upload/manager.js.map +1 -1
  130. package/dist/internal/upload/streamUploader.d.ts +1 -1
  131. package/dist/internal/upload/streamUploader.js +12 -13
  132. package/dist/internal/upload/streamUploader.js.map +1 -1
  133. package/dist/internal/upload/streamUploader.test.js +28 -4
  134. package/dist/internal/upload/streamUploader.test.js.map +1 -1
  135. package/dist/internal/utils.d.ts +1 -1
  136. package/dist/protonDriveClient.d.ts +8 -0
  137. package/dist/protonDriveClient.js +12 -1
  138. package/dist/protonDriveClient.js.map +1 -1
  139. package/dist/protonDrivePhotosClient.d.ts +35 -3
  140. package/dist/protonDrivePhotosClient.js +42 -2
  141. package/dist/protonDrivePhotosClient.js.map +1 -1
  142. package/dist/protonDrivePublicLinkClient.js +1 -1
  143. package/dist/protonDrivePublicLinkClient.js.map +1 -1
  144. package/dist/transformers.js +2 -0
  145. package/dist/transformers.js.map +1 -1
  146. package/package.json +4 -4
  147. package/src/crypto/driveCrypto.test.ts +2 -1
  148. package/src/crypto/driveCrypto.ts +50 -16
  149. package/src/crypto/hmac.ts +4 -4
  150. package/src/crypto/interface.ts +58 -27
  151. package/src/crypto/openPGPCrypto.ts +26 -26
  152. package/src/diagnostic/telemetry.ts +3 -0
  153. package/src/interface/index.ts +11 -2
  154. package/src/interface/nodes.ts +1 -0
  155. package/src/interface/photos.ts +33 -2
  156. package/src/interface/sharing.ts +2 -0
  157. package/src/interface/telemetry.ts +15 -1
  158. package/src/interface/thumbnail.ts +2 -2
  159. package/src/internal/apiService/apiService.test.ts +38 -6
  160. package/src/internal/apiService/apiService.ts +39 -14
  161. package/src/internal/apiService/driveTypes.ts +2942 -3187
  162. package/src/internal/devices/manager.ts +14 -0
  163. package/src/internal/download/apiService.ts +1 -1
  164. package/src/internal/download/cryptoService.ts +4 -4
  165. package/src/internal/download/fileDownloader.test.ts +4 -4
  166. package/src/internal/download/fileDownloader.ts +6 -6
  167. package/src/internal/download/thumbnailDownloader.ts +4 -4
  168. package/src/internal/nodes/apiService.ts +2 -2
  169. package/src/internal/nodes/cryptoService.test.ts +2 -2
  170. package/src/internal/nodes/cryptoService.ts +11 -8
  171. package/src/internal/nodes/interface.ts +1 -1
  172. package/src/internal/nodes/nodesAccess.ts +1 -1
  173. package/src/internal/nodes/nodesManagement.ts +0 -1
  174. package/src/internal/photos/addToAlbum.test.ts +515 -0
  175. package/src/internal/photos/addToAlbum.ts +234 -0
  176. package/src/internal/photos/albumsCrypto.ts +54 -3
  177. package/src/internal/photos/{albums.test.ts → albumsManager.test.ts} +22 -25
  178. package/src/internal/photos/{albums.ts → albumsManager.ts} +32 -3
  179. package/src/internal/photos/apiService.test.ts +233 -0
  180. package/src/internal/photos/apiService.ts +228 -26
  181. package/src/internal/photos/errors.ts +11 -0
  182. package/src/internal/photos/index.ts +6 -3
  183. package/src/internal/photos/interface.ts +8 -18
  184. package/src/internal/photos/nodes.test.ts +27 -6
  185. package/src/internal/photos/nodes.ts +35 -3
  186. package/src/internal/photos/photosManager.test.ts +266 -0
  187. package/src/internal/photos/photosManager.ts +144 -0
  188. package/src/internal/photos/photosTransferPayloadBuilder.test.ts +380 -0
  189. package/src/internal/photos/photosTransferPayloadBuilder.ts +203 -0
  190. package/src/internal/photos/upload.ts +8 -3
  191. package/src/internal/shares/apiService.ts +1 -0
  192. package/src/internal/shares/interface.ts +1 -0
  193. package/src/internal/sharing/apiService.ts +49 -5
  194. package/src/internal/sharing/cryptoService.ts +10 -4
  195. package/src/internal/sharing/sharingManagement.test.ts +33 -5
  196. package/src/internal/sharing/sharingManagement.ts +28 -6
  197. package/src/internal/sharingPublic/nodes.ts +1 -1
  198. package/src/internal/upload/apiService.ts +10 -12
  199. package/src/internal/upload/blockVerifier.ts +3 -3
  200. package/src/internal/upload/chunkStreamReader.test.ts +7 -7
  201. package/src/internal/upload/chunkStreamReader.ts +3 -3
  202. package/src/internal/upload/cryptoService.ts +11 -12
  203. package/src/internal/upload/interface.ts +6 -6
  204. package/src/internal/upload/manager.ts +2 -2
  205. package/src/internal/upload/streamUploader.test.ts +33 -4
  206. package/src/internal/upload/streamUploader.ts +13 -13
  207. package/src/protonDriveClient.ts +16 -4
  208. package/src/protonDrivePhotosClient.ts +73 -17
  209. package/src/protonDrivePublicLinkClient.ts +1 -1
  210. package/src/transformers.ts +2 -0
  211. package/dist/internal/photos/albums.js.map +0 -1
  212. package/dist/internal/photos/albums.test.js.map +0 -1
  213. /package/dist/internal/photos/{albums.test.d.ts → addToAlbum.test.d.ts} +0 -0
@@ -1,5 +1,5 @@
1
1
  import { AbortError } from '../../errors';
2
- import { ProtonDriveHTTPClient, SDKEvent } from '../../interface';
2
+ import { ProtonDriveHTTPClient, SDKEvent, Telemetry, MetricEvent } from '../../interface';
3
3
  import { getMockTelemetry } from '../../tests/telemetry';
4
4
  import { SDKEvents } from '../sdkEvents';
5
5
  import { DriveAPIService } from './apiService';
@@ -12,13 +12,17 @@ function generateOkResponse() {
12
12
  }
13
13
 
14
14
  describe('DriveAPIService', () => {
15
+ let telemetry: Telemetry<MetricEvent>;
15
16
  let sdkEvents: SDKEvents;
16
17
  let httpClient: ProtonDriveHTTPClient;
17
18
  let api: DriveAPIService;
18
19
 
20
+ const baseUrl = 'https://drive.proton.me';
21
+
19
22
  beforeEach(() => {
20
23
  void jest.runAllTimersAsync();
21
24
 
25
+ telemetry = getMockTelemetry();
22
26
  // @ts-expect-error: No need to implement all methods for mocking
23
27
  sdkEvents = {
24
28
  transfersPaused: jest.fn(),
@@ -30,7 +34,7 @@ describe('DriveAPIService', () => {
30
34
  fetchJson: jest.fn(() => Promise.resolve(generateOkResponse())),
31
35
  fetchBlob: jest.fn(() => Promise.resolve(new Response(new Uint8Array([1, 2, 3])))),
32
36
  };
33
- api = new DriveAPIService(getMockTelemetry(), sdkEvents, httpClient, 'http://drive.proton.me', 'en');
37
+ api = new DriveAPIService(telemetry, sdkEvents, httpClient, baseUrl, 'en');
34
38
  });
35
39
 
36
40
  function expectSDKEvents(...events: SDKEvent[]) {
@@ -42,6 +46,15 @@ describe('DriveAPIService', () => {
42
46
  );
43
47
  }
44
48
 
49
+ function expectMetricEvent(previousError: unknown, failedAttempts: number) {
50
+ expect(telemetry.recordMetric).toHaveBeenCalledWith({
51
+ eventName: 'apiRetrySucceeded',
52
+ failedAttempts,
53
+ url: `${baseUrl}/test`,
54
+ previousError,
55
+ });
56
+ }
57
+
45
58
  describe('should make', () => {
46
59
  it('GET request', async () => {
47
60
  const result = await api.get('test');
@@ -78,6 +91,7 @@ describe('DriveAPIService', () => {
78
91
  );
79
92
  expect(await request.json).toEqual(data);
80
93
  expectSDKEvents();
94
+ expect(telemetry.recordMetric).not.toHaveBeenCalled();
81
95
  }
82
96
 
83
97
  it('storage GET request', async () => {
@@ -109,6 +123,7 @@ describe('DriveAPIService', () => {
109
123
  );
110
124
  expect(request.body).toEqual(data);
111
125
  expectSDKEvents();
126
+ expect(telemetry.recordMetric).not.toHaveBeenCalled();
112
127
  }
113
128
  });
114
129
 
@@ -121,6 +136,7 @@ describe('DriveAPIService', () => {
121
136
 
122
137
  await expect(api.get('test')).rejects.toThrow(new AbortError('Request aborted'));
123
138
  expectSDKEvents();
139
+ expect(telemetry.recordMetric).not.toHaveBeenCalled();
124
140
  });
125
141
 
126
142
  it('APIHTTPError on 4xx response without JSON body', async () => {
@@ -129,6 +145,7 @@ describe('DriveAPIService', () => {
129
145
  );
130
146
  await expect(api.get('test')).rejects.toThrow(new Error('Not found'));
131
147
  expectSDKEvents();
148
+ expect(telemetry.recordMetric).not.toHaveBeenCalled();
132
149
  });
133
150
 
134
151
  it('APIError on 4xx response with JSON body', async () => {
@@ -137,6 +154,7 @@ describe('DriveAPIService', () => {
137
154
  );
138
155
  await expect(api.get('test')).rejects.toThrow('General error');
139
156
  expectSDKEvents();
157
+ expect(telemetry.recordMetric).not.toHaveBeenCalled();
140
158
  });
141
159
  });
142
160
 
@@ -155,6 +173,7 @@ describe('DriveAPIService', () => {
155
173
  await expect(result).resolves.toEqual({ Code: ErrorCode.OK });
156
174
  expect(httpClient.fetchJson).toHaveBeenCalledTimes(3);
157
175
  expectSDKEvents();
176
+ expect(telemetry.recordMetric).not.toHaveBeenCalled();
158
177
  });
159
178
 
160
179
  it('on timeout error', async () => {
@@ -171,19 +190,19 @@ describe('DriveAPIService', () => {
171
190
  await expect(result).resolves.toEqual({ Code: ErrorCode.OK });
172
191
  expect(httpClient.fetchJson).toHaveBeenCalledTimes(3);
173
192
  expectSDKEvents();
193
+ expect(telemetry.recordMetric).not.toHaveBeenCalled();
174
194
  });
175
195
 
176
196
  it('on general error', async () => {
177
- httpClient.fetchJson = jest
178
- .fn()
179
- .mockRejectedValueOnce(new Error('Error'))
180
- .mockResolvedValueOnce(generateOkResponse());
197
+ const error = new Error('Error');
198
+ httpClient.fetchJson = jest.fn().mockRejectedValueOnce(error).mockResolvedValueOnce(generateOkResponse());
181
199
 
182
200
  const result = api.get('test');
183
201
 
184
202
  await expect(result).resolves.toEqual({ Code: ErrorCode.OK });
185
203
  expect(httpClient.fetchJson).toHaveBeenCalledTimes(2);
186
204
  expectSDKEvents();
205
+ expectMetricEvent(error, 1);
187
206
  });
188
207
 
189
208
  it('only once on general error', async () => {
@@ -198,6 +217,7 @@ describe('DriveAPIService', () => {
198
217
  await expect(result).rejects.toThrow('Second error');
199
218
  expect(httpClient.fetchJson).toHaveBeenCalledTimes(2);
200
219
  expectSDKEvents();
220
+ expect(telemetry.recordMetric).not.toHaveBeenCalled();
201
221
  });
202
222
 
203
223
  it('on 429 response with default timeout', async () => {
@@ -231,6 +251,7 @@ describe('DriveAPIService', () => {
231
251
 
232
252
  await expect(result).resolves.toEqual({ Code: ErrorCode.OK });
233
253
  expectSDKEvents();
254
+ expectMetricEvent(429, 2);
234
255
  });
235
256
 
236
257
  it('on 429 response with retry-after header', async () => {
@@ -272,6 +293,7 @@ describe('DriveAPIService', () => {
272
293
 
273
294
  await expect(result).resolves.toEqual({ Code: ErrorCode.OK });
274
295
  expectSDKEvents();
296
+ expectMetricEvent(429, 2);
275
297
  });
276
298
 
277
299
  it('on 5xx response', async () => {
@@ -287,6 +309,7 @@ describe('DriveAPIService', () => {
287
309
  await expect(result).resolves.toEqual({ Code: ErrorCode.OK });
288
310
  expect(httpClient.fetchJson).toHaveBeenCalledTimes(2);
289
311
  expectSDKEvents();
312
+ expectMetricEvent(500, 1);
290
313
  });
291
314
 
292
315
  it('only once on 5xx response', async () => {
@@ -301,6 +324,7 @@ describe('DriveAPIService', () => {
301
324
  await expect(result).rejects.toThrow('Some error');
302
325
  expect(httpClient.fetchJson).toHaveBeenCalledTimes(2);
303
326
  expectSDKEvents();
327
+ expect(telemetry.recordMetric).not.toHaveBeenCalled();
304
328
  });
305
329
  });
306
330
 
@@ -314,6 +338,7 @@ describe('DriveAPIService', () => {
314
338
  await expect(api.get('test')).rejects.toThrow(error);
315
339
  expect(httpClient.fetchJson).toHaveBeenCalledTimes(3);
316
340
  expectSDKEvents();
341
+ expect(telemetry.recordMetric).not.toHaveBeenCalled();
317
342
  });
318
343
 
319
344
  it('limit 429 errors', async () => {
@@ -336,6 +361,8 @@ describe('DriveAPIService', () => {
336
361
  httpClient.fetchJson = jest.fn().mockResolvedValue(generateOkResponse());
337
362
  await api.get('test');
338
363
  expect(sdkEvents.requestsThrottled).toHaveBeenCalledTimes(1);
364
+
365
+ expect(telemetry.recordMetric).not.toHaveBeenCalled();
339
366
  });
340
367
 
341
368
  it('do not limit 429s when some pass', async () => {
@@ -355,6 +382,7 @@ describe('DriveAPIService', () => {
355
382
  // 20 calls * 5 retries till OK response + 1 last successful call
356
383
  expect(httpClient.fetchJson).toHaveBeenCalledTimes(101);
357
384
  expectSDKEvents();
385
+ expectMetricEvent(429, 4);
358
386
  });
359
387
 
360
388
  it('limit server errors', async () => {
@@ -371,6 +399,7 @@ describe('DriveAPIService', () => {
371
399
  await expect(api.get('test')).rejects.toThrow('Too many server errors, please try again later');
372
400
  expect(httpClient.fetchJson).toHaveBeenCalledTimes(10);
373
401
  expectSDKEvents();
402
+ expect(telemetry.recordMetric).not.toHaveBeenCalled();
374
403
  });
375
404
 
376
405
  it('do not limit server errors when some pass', async () => {
@@ -390,6 +419,7 @@ describe('DriveAPIService', () => {
390
419
  // 15 erroring calls * 2 attempts + 5 successful calls
391
420
  expect(httpClient.fetchJson).toHaveBeenCalledTimes(35);
392
421
  expectSDKEvents();
422
+ expect(telemetry.recordMetric).not.toHaveBeenCalled();
393
423
  });
394
424
 
395
425
  it('notify about offline error', async () => {
@@ -428,6 +458,8 @@ describe('DriveAPIService', () => {
428
458
  expectSDKEvents(SDKEvent.TransfersPaused, SDKEvent.TransfersResumed);
429
459
 
430
460
  await promise;
461
+
462
+ expect(telemetry.recordMetric).not.toHaveBeenCalled();
431
463
  });
432
464
  });
433
465
  });
@@ -137,8 +137,12 @@ export class DriveAPIService {
137
137
  return this.makeRequest(url, 'PUT', data, signal);
138
138
  }
139
139
 
140
- async delete<Response>(url: string, signal?: AbortSignal): Promise<Response> {
141
- return this.makeRequest(url, 'DELETE', undefined, signal);
140
+ async delete<RequestPayload, ResponsePayload>(
141
+ url: string,
142
+ data?: RequestPayload,
143
+ signal?: AbortSignal,
144
+ ): Promise<ResponsePayload> {
145
+ return this.makeRequest(url, 'DELETE', data, signal);
142
146
  }
143
147
 
144
148
  protected async makeRequest<RequestPayload, ResponsePayload>(
@@ -245,7 +249,15 @@ export class DriveAPIService {
245
249
  private async fetch(
246
250
  request: { method: string; url: string; signal?: AbortSignal },
247
251
  callback: () => Promise<Response>,
248
- attempt = 0,
252
+ {
253
+ attempt,
254
+ previousError,
255
+ }: {
256
+ attempt: number;
257
+ previousError?: unknown;
258
+ } = {
259
+ attempt: 0,
260
+ },
249
261
  ): Promise<Response> {
250
262
  if (request.signal?.aborted) {
251
263
  throw new AbortError(c('Error').t`Request aborted`);
@@ -282,19 +294,19 @@ export class DriveAPIService {
282
294
  this.offlineErrorHappened();
283
295
  this.logger.info(`${request.method} ${request.url}: Offline error, retrying`);
284
296
  await waitSeconds(OFFLINE_RETRY_DELAY_SECONDS);
285
- return this.fetch(request, callback, attempt + 1);
297
+ return this.fetch(request, callback, { attempt: attempt + 1, previousError: error });
286
298
  }
287
299
 
288
300
  if (error.name === 'TimeoutError' && attempt + 1 < MAX_TIMEOUT_ERROR_RETRY_ATTEMPTS) {
289
301
  this.logger.warn(`${request.method} ${request.url}: Timeout error, retrying`);
290
302
  await waitSeconds(SERVER_ERROR_RETRY_DELAY_SECONDS);
291
- return this.fetch(request, callback, attempt + 1);
303
+ return this.fetch(request, callback, { attempt: attempt + 1, previousError: error });
292
304
  }
293
305
  }
294
306
  if (attempt === 0) {
295
307
  this.logger.error(`${request.method} ${request.url}: failed, retrying once`, error);
296
308
  await waitSeconds(GENERAL_RETRY_DELAY_SECONDS);
297
- return this.fetch(request, callback, attempt + 1);
309
+ return this.fetch(request, callback, { attempt: attempt + 1, previousError: error });
298
310
  }
299
311
  this.logger.error(`${request.method} ${request.url}: failed`, error);
300
312
  throw error;
@@ -315,7 +327,7 @@ export class DriveAPIService {
315
327
  this.tooManyRequestsErrorHappened();
316
328
  const timeout = parseInt(response.headers.get('retry-after') || '0', 10) || DEFAULT_429_RETRY_DELAY_SECONDS;
317
329
  await waitSeconds(timeout);
318
- return this.fetch(request, callback, attempt + 1);
330
+ return this.fetch(request, callback, { attempt: attempt + 1, previousError: response.status });
319
331
  } else {
320
332
  this.clearSubsequentTooManyRequestsError();
321
333
  }
@@ -329,16 +341,29 @@ export class DriveAPIService {
329
341
  this.logger.warn(`${request.method} ${request.url}: ${response.status} - retry failed`);
330
342
  } else {
331
343
  await waitSeconds(SERVER_ERROR_RETRY_DELAY_SECONDS);
332
- return this.fetch(request, callback, attempt + 1);
344
+ return this.fetch(request, callback, { attempt: attempt + 1, previousError: response.status });
333
345
  }
334
346
  } else {
335
347
  if (attempt > 0) {
336
- this.telemetry.recordMetric({
337
- eventName: 'apiRetrySucceeded',
338
- failedAttempts: attempt,
339
- url: request.url,
340
- });
341
- this.logger.warn(`${request.method} ${request.url}: ${response.status} - retry helped`);
348
+ const previousErrorMessage =
349
+ previousError instanceof Error ? previousError.message : String(previousError);
350
+ const isWarning =
351
+ !(previousError instanceof Error) ||
352
+ (previousError instanceof Error &&
353
+ previousError.name !== 'TimeoutError' &&
354
+ previousError.name !== 'OfflineError');
355
+
356
+ if (isWarning) {
357
+ this.telemetry.recordMetric({
358
+ eventName: 'apiRetrySucceeded',
359
+ failedAttempts: attempt,
360
+ url: request.url,
361
+ previousError,
362
+ });
363
+ this.logger.warn(`${request.method} ${request.url}: ${previousErrorMessage} - retry helped`);
364
+ } else {
365
+ this.logger.debug(`${request.method} ${request.url}: ${previousErrorMessage} - retry helped`);
366
+ }
342
367
  }
343
368
  this.clearSubsequentServerErrors();
344
369
  }