@protontech/drive-sdk 0.9.8 → 0.10.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 (174) hide show
  1. package/dist/crypto/driveCrypto.d.ts +15 -15
  2. package/dist/crypto/driveCrypto.js.map +1 -1
  3. package/dist/crypto/hmac.d.ts +3 -3
  4. package/dist/crypto/hmac.js.map +1 -1
  5. package/dist/crypto/interface.d.ts +45 -25
  6. package/dist/crypto/interface.js.map +1 -1
  7. package/dist/crypto/openPGPCrypto.d.ts +37 -37
  8. package/dist/crypto/openPGPCrypto.js.map +1 -1
  9. package/dist/crypto/utils.d.ts +1 -1
  10. package/dist/interface/index.d.ts +3 -3
  11. package/dist/interface/index.js.map +1 -1
  12. package/dist/interface/nodes.d.ts +8 -0
  13. package/dist/interface/photos.d.ts +18 -1
  14. package/dist/interface/sharing.d.ts +2 -0
  15. package/dist/interface/telemetry.d.ts +1 -0
  16. package/dist/interface/telemetry.js.map +1 -1
  17. package/dist/interface/thumbnail.d.ts +2 -2
  18. package/dist/internal/apiService/apiService.js +25 -12
  19. package/dist/internal/apiService/apiService.js.map +1 -1
  20. package/dist/internal/apiService/apiService.test.js +33 -5
  21. package/dist/internal/apiService/apiService.test.js.map +1 -1
  22. package/dist/internal/apiService/driveTypes.d.ts +2942 -3187
  23. package/dist/internal/apiService/errors.test.js +17 -7
  24. package/dist/internal/apiService/errors.test.js.map +1 -1
  25. package/dist/internal/devices/manager.d.ts +1 -0
  26. package/dist/internal/devices/manager.js +11 -0
  27. package/dist/internal/devices/manager.js.map +1 -1
  28. package/dist/internal/download/apiService.d.ts +1 -1
  29. package/dist/internal/download/cryptoService.d.ts +4 -4
  30. package/dist/internal/download/cryptoService.js.map +1 -1
  31. package/dist/internal/download/fileDownloader.js.map +1 -1
  32. package/dist/internal/download/fileDownloader.test.js.map +1 -1
  33. package/dist/internal/download/thumbnailDownloader.js.map +1 -1
  34. package/dist/internal/nodes/cryptoService.d.ts +4 -4
  35. package/dist/internal/nodes/cryptoService.js +5 -3
  36. package/dist/internal/nodes/cryptoService.js.map +1 -1
  37. package/dist/internal/nodes/cryptoService.test.js.map +1 -1
  38. package/dist/internal/nodes/interface.d.ts +1 -1
  39. package/dist/internal/nodes/nodesManagement.js +0 -1
  40. package/dist/internal/nodes/nodesManagement.js.map +1 -1
  41. package/dist/internal/photos/addToAlbum.d.ts +46 -0
  42. package/dist/internal/photos/addToAlbum.js +257 -0
  43. package/dist/internal/photos/addToAlbum.js.map +1 -0
  44. package/dist/internal/photos/addToAlbum.test.d.ts +1 -0
  45. package/dist/internal/photos/addToAlbum.test.js +409 -0
  46. package/dist/internal/photos/addToAlbum.test.js.map +1 -0
  47. package/dist/internal/photos/albums.d.ts +7 -2
  48. package/dist/internal/photos/albums.js +24 -1
  49. package/dist/internal/photos/albums.js.map +1 -1
  50. package/dist/internal/photos/albums.test.js +26 -1
  51. package/dist/internal/photos/albums.test.js.map +1 -1
  52. package/dist/internal/photos/albumsCrypto.d.ts +20 -3
  53. package/dist/internal/photos/albumsCrypto.js +27 -0
  54. package/dist/internal/photos/albumsCrypto.js.map +1 -1
  55. package/dist/internal/photos/apiService.d.ts +20 -0
  56. package/dist/internal/photos/apiService.js +142 -0
  57. package/dist/internal/photos/apiService.js.map +1 -1
  58. package/dist/internal/photos/apiService.test.d.ts +1 -0
  59. package/dist/internal/photos/apiService.test.js +199 -0
  60. package/dist/internal/photos/apiService.test.js.map +1 -0
  61. package/dist/internal/photos/errors.d.ts +4 -0
  62. package/dist/internal/photos/errors.js +17 -0
  63. package/dist/internal/photos/errors.js.map +1 -0
  64. package/dist/internal/photos/index.d.ts +1 -1
  65. package/dist/internal/photos/index.js +1 -1
  66. package/dist/internal/photos/index.js.map +1 -1
  67. package/dist/internal/photos/interface.d.ts +36 -1
  68. package/dist/internal/photos/interface.js +14 -0
  69. package/dist/internal/photos/interface.js.map +1 -1
  70. package/dist/internal/photos/nodes.js +32 -2
  71. package/dist/internal/photos/nodes.js.map +1 -1
  72. package/dist/internal/photos/nodes.test.js +25 -5
  73. package/dist/internal/photos/nodes.test.js.map +1 -1
  74. package/dist/internal/photos/timeline.d.ts +2 -5
  75. package/dist/internal/photos/timeline.js.map +1 -1
  76. package/dist/internal/photos/upload.d.ts +2 -2
  77. package/dist/internal/photos/upload.js.map +1 -1
  78. package/dist/internal/shares/apiService.js +1 -0
  79. package/dist/internal/shares/apiService.js.map +1 -1
  80. package/dist/internal/shares/interface.d.ts +1 -0
  81. package/dist/internal/sharing/apiService.d.ts +8 -1
  82. package/dist/internal/sharing/apiService.js +23 -1
  83. package/dist/internal/sharing/apiService.js.map +1 -1
  84. package/dist/internal/sharing/cryptoService.js +8 -4
  85. package/dist/internal/sharing/cryptoService.js.map +1 -1
  86. package/dist/internal/sharing/sharingManagement.d.ts +1 -0
  87. package/dist/internal/sharing/sharingManagement.js +15 -2
  88. package/dist/internal/sharing/sharingManagement.js.map +1 -1
  89. package/dist/internal/sharing/sharingManagement.test.js +30 -5
  90. package/dist/internal/sharing/sharingManagement.test.js.map +1 -1
  91. package/dist/internal/sharingPublic/nodes.d.ts +2 -2
  92. package/dist/internal/sharingPublic/nodes.js.map +1 -1
  93. package/dist/internal/upload/apiService.d.ts +5 -5
  94. package/dist/internal/upload/apiService.js.map +1 -1
  95. package/dist/internal/upload/blockVerifier.d.ts +2 -2
  96. package/dist/internal/upload/blockVerifier.js.map +1 -1
  97. package/dist/internal/upload/chunkStreamReader.d.ts +2 -2
  98. package/dist/internal/upload/chunkStreamReader.js.map +1 -1
  99. package/dist/internal/upload/chunkStreamReader.test.js.map +1 -1
  100. package/dist/internal/upload/cryptoService.d.ts +7 -7
  101. package/dist/internal/upload/cryptoService.js.map +1 -1
  102. package/dist/internal/upload/interface.d.ts +6 -6
  103. package/dist/internal/upload/manager.d.ts +1 -1
  104. package/dist/internal/upload/manager.js.map +1 -1
  105. package/dist/internal/upload/streamUploader.d.ts +1 -1
  106. package/dist/internal/utils.d.ts +1 -1
  107. package/dist/protonDriveClient.d.ts +8 -0
  108. package/dist/protonDriveClient.js +11 -0
  109. package/dist/protonDriveClient.js.map +1 -1
  110. package/dist/protonDrivePhotosClient.d.ts +42 -7
  111. package/dist/protonDrivePhotosClient.js +50 -2
  112. package/dist/protonDrivePhotosClient.js.map +1 -1
  113. package/dist/protonDrivePublicLinkClient.d.ts +9 -0
  114. package/dist/protonDrivePublicLinkClient.js +12 -0
  115. package/dist/protonDrivePublicLinkClient.js.map +1 -1
  116. package/dist/transformers.js +2 -0
  117. package/dist/transformers.js.map +1 -1
  118. package/package.json +4 -4
  119. package/src/crypto/driveCrypto.ts +15 -15
  120. package/src/crypto/hmac.ts +4 -4
  121. package/src/crypto/interface.ts +58 -27
  122. package/src/crypto/openPGPCrypto.ts +26 -26
  123. package/src/interface/index.ts +10 -2
  124. package/src/interface/nodes.ts +1 -0
  125. package/src/interface/photos.ts +19 -1
  126. package/src/interface/sharing.ts +2 -0
  127. package/src/interface/telemetry.ts +1 -0
  128. package/src/interface/thumbnail.ts +2 -2
  129. package/src/internal/apiService/apiService.test.ts +38 -6
  130. package/src/internal/apiService/apiService.ts +33 -12
  131. package/src/internal/apiService/driveTypes.ts +2942 -3187
  132. package/src/internal/devices/manager.ts +14 -0
  133. package/src/internal/download/apiService.ts +1 -1
  134. package/src/internal/download/cryptoService.ts +4 -4
  135. package/src/internal/download/fileDownloader.test.ts +4 -4
  136. package/src/internal/download/fileDownloader.ts +6 -6
  137. package/src/internal/download/thumbnailDownloader.ts +4 -4
  138. package/src/internal/nodes/cryptoService.test.ts +2 -2
  139. package/src/internal/nodes/cryptoService.ts +11 -8
  140. package/src/internal/nodes/interface.ts +1 -1
  141. package/src/internal/nodes/nodesManagement.ts +0 -1
  142. package/src/internal/photos/addToAlbum.test.ts +515 -0
  143. package/src/internal/photos/addToAlbum.ts +341 -0
  144. package/src/internal/photos/albums.test.ts +46 -22
  145. package/src/internal/photos/albums.ts +48 -2
  146. package/src/internal/photos/albumsCrypto.ts +54 -3
  147. package/src/internal/photos/apiService.test.ts +233 -0
  148. package/src/internal/photos/apiService.ts +234 -15
  149. package/src/internal/photos/errors.ts +11 -0
  150. package/src/internal/photos/index.ts +2 -2
  151. package/src/internal/photos/interface.ts +40 -1
  152. package/src/internal/photos/nodes.test.ts +27 -6
  153. package/src/internal/photos/nodes.ts +34 -2
  154. package/src/internal/photos/timeline.ts +2 -5
  155. package/src/internal/photos/upload.ts +2 -2
  156. package/src/internal/shares/apiService.ts +1 -0
  157. package/src/internal/shares/interface.ts +1 -0
  158. package/src/internal/sharing/apiService.ts +49 -5
  159. package/src/internal/sharing/cryptoService.ts +10 -4
  160. package/src/internal/sharing/sharingManagement.test.ts +33 -5
  161. package/src/internal/sharing/sharingManagement.ts +28 -6
  162. package/src/internal/sharingPublic/nodes.ts +1 -1
  163. package/src/internal/upload/apiService.ts +5 -5
  164. package/src/internal/upload/blockVerifier.ts +3 -3
  165. package/src/internal/upload/chunkStreamReader.test.ts +7 -7
  166. package/src/internal/upload/chunkStreamReader.ts +3 -3
  167. package/src/internal/upload/cryptoService.ts +9 -9
  168. package/src/internal/upload/interface.ts +6 -6
  169. package/src/internal/upload/manager.ts +2 -2
  170. package/src/internal/upload/streamUploader.ts +1 -1
  171. package/src/protonDriveClient.ts +15 -3
  172. package/src/protonDrivePhotosClient.ts +78 -22
  173. package/src/protonDrivePublicLinkClient.ts +13 -0
  174. package/src/transformers.ts +2 -0
@@ -24,6 +24,7 @@ export interface MetricAPIRetrySucceededEvent {
24
24
  eventName: 'apiRetrySucceeded';
25
25
  url: string;
26
26
  failedAttempts: number;
27
+ previousError?: unknown;
27
28
  }
28
29
 
29
30
  export interface MetricDebounceLongWaitEvent {
@@ -1,6 +1,6 @@
1
1
  export type Thumbnail = {
2
2
  type: ThumbnailType;
3
- thumbnail: Uint8Array;
3
+ thumbnail: Uint8Array<ArrayBuffer>;
4
4
  };
5
5
 
6
6
  export enum ThumbnailType {
@@ -9,5 +9,5 @@ export enum ThumbnailType {
9
9
  }
10
10
 
11
11
  export type ThumbnailResult =
12
- | { nodeUid: string; ok: true; thumbnail: Uint8Array }
12
+ | { nodeUid: string; ok: true; thumbnail: Uint8Array<ArrayBuffer> }
13
13
  | { nodeUid: string; ok: false; error: string };
@@ -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
  });
@@ -245,7 +245,15 @@ export class DriveAPIService {
245
245
  private async fetch(
246
246
  request: { method: string; url: string; signal?: AbortSignal },
247
247
  callback: () => Promise<Response>,
248
- attempt = 0,
248
+ {
249
+ attempt,
250
+ previousError,
251
+ }: {
252
+ attempt: number;
253
+ previousError?: unknown;
254
+ } = {
255
+ attempt: 0,
256
+ },
249
257
  ): Promise<Response> {
250
258
  if (request.signal?.aborted) {
251
259
  throw new AbortError(c('Error').t`Request aborted`);
@@ -282,19 +290,19 @@ export class DriveAPIService {
282
290
  this.offlineErrorHappened();
283
291
  this.logger.info(`${request.method} ${request.url}: Offline error, retrying`);
284
292
  await waitSeconds(OFFLINE_RETRY_DELAY_SECONDS);
285
- return this.fetch(request, callback, attempt + 1);
293
+ return this.fetch(request, callback, { attempt: attempt + 1, previousError: error });
286
294
  }
287
295
 
288
296
  if (error.name === 'TimeoutError' && attempt + 1 < MAX_TIMEOUT_ERROR_RETRY_ATTEMPTS) {
289
297
  this.logger.warn(`${request.method} ${request.url}: Timeout error, retrying`);
290
298
  await waitSeconds(SERVER_ERROR_RETRY_DELAY_SECONDS);
291
- return this.fetch(request, callback, attempt + 1);
299
+ return this.fetch(request, callback, { attempt: attempt + 1, previousError: error });
292
300
  }
293
301
  }
294
302
  if (attempt === 0) {
295
303
  this.logger.error(`${request.method} ${request.url}: failed, retrying once`, error);
296
304
  await waitSeconds(GENERAL_RETRY_DELAY_SECONDS);
297
- return this.fetch(request, callback, attempt + 1);
305
+ return this.fetch(request, callback, { attempt: attempt + 1, previousError: error });
298
306
  }
299
307
  this.logger.error(`${request.method} ${request.url}: failed`, error);
300
308
  throw error;
@@ -315,7 +323,7 @@ export class DriveAPIService {
315
323
  this.tooManyRequestsErrorHappened();
316
324
  const timeout = parseInt(response.headers.get('retry-after') || '0', 10) || DEFAULT_429_RETRY_DELAY_SECONDS;
317
325
  await waitSeconds(timeout);
318
- return this.fetch(request, callback, attempt + 1);
326
+ return this.fetch(request, callback, { attempt: attempt + 1, previousError: response.status });
319
327
  } else {
320
328
  this.clearSubsequentTooManyRequestsError();
321
329
  }
@@ -329,16 +337,29 @@ export class DriveAPIService {
329
337
  this.logger.warn(`${request.method} ${request.url}: ${response.status} - retry failed`);
330
338
  } else {
331
339
  await waitSeconds(SERVER_ERROR_RETRY_DELAY_SECONDS);
332
- return this.fetch(request, callback, attempt + 1);
340
+ return this.fetch(request, callback, { attempt: attempt + 1, previousError: response.status });
333
341
  }
334
342
  } else {
335
343
  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`);
344
+ const previousErrorMessage =
345
+ previousError instanceof Error ? previousError.message : String(previousError);
346
+ const isWarning =
347
+ !(previousError instanceof Error) ||
348
+ (previousError instanceof Error &&
349
+ previousError.name !== 'TimeoutError' &&
350
+ previousError.name !== 'OfflineError');
351
+
352
+ if (isWarning) {
353
+ this.telemetry.recordMetric({
354
+ eventName: 'apiRetrySucceeded',
355
+ failedAttempts: attempt,
356
+ url: request.url,
357
+ previousError,
358
+ });
359
+ this.logger.warn(`${request.method} ${request.url}: ${previousErrorMessage} - retry helped`);
360
+ } else {
361
+ this.logger.debug(`${request.method} ${request.url}: ${previousErrorMessage} - retry helped`);
362
+ }
342
363
  }
343
364
  this.clearSubsequentServerErrors();
344
365
  }