@protontech/drive-sdk 0.0.13 → 0.1.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 (234) hide show
  1. package/dist/cache/index.d.ts +1 -0
  2. package/dist/cache/index.js +3 -1
  3. package/dist/cache/index.js.map +1 -1
  4. package/dist/cache/memoryCache.d.ts +1 -1
  5. package/dist/cache/nullCache.d.ts +14 -0
  6. package/dist/cache/nullCache.js +37 -0
  7. package/dist/cache/nullCache.js.map +1 -0
  8. package/dist/config.d.ts +16 -1
  9. package/dist/config.js +1 -1
  10. package/dist/config.js.map +1 -1
  11. package/dist/crypto/openPGPCrypto.js +2 -0
  12. package/dist/crypto/openPGPCrypto.js.map +1 -1
  13. package/dist/diagnostic/eventsGenerator.d.ts +14 -0
  14. package/dist/diagnostic/eventsGenerator.js +49 -0
  15. package/dist/diagnostic/eventsGenerator.js.map +1 -0
  16. package/dist/diagnostic/httpClient.d.ts +16 -0
  17. package/dist/diagnostic/httpClient.js +81 -0
  18. package/dist/diagnostic/httpClient.js.map +1 -0
  19. package/dist/diagnostic/index.d.ts +10 -0
  20. package/dist/diagnostic/index.js +35 -0
  21. package/dist/diagnostic/index.js.map +1 -0
  22. package/dist/diagnostic/integrityVerificationStream.d.ts +21 -0
  23. package/dist/diagnostic/integrityVerificationStream.js +56 -0
  24. package/dist/diagnostic/integrityVerificationStream.js.map +1 -0
  25. package/dist/diagnostic/interface.d.ts +102 -0
  26. package/dist/diagnostic/interface.js +3 -0
  27. package/dist/diagnostic/interface.js.map +1 -0
  28. package/dist/diagnostic/sdkDiagnostic.d.ts +22 -0
  29. package/dist/diagnostic/sdkDiagnostic.js +216 -0
  30. package/dist/diagnostic/sdkDiagnostic.js.map +1 -0
  31. package/dist/diagnostic/sdkDiagnosticFull.d.ts +18 -0
  32. package/dist/diagnostic/sdkDiagnosticFull.js +35 -0
  33. package/dist/diagnostic/sdkDiagnosticFull.js.map +1 -0
  34. package/dist/diagnostic/telemetry.d.ts +25 -0
  35. package/dist/diagnostic/telemetry.js +70 -0
  36. package/dist/diagnostic/telemetry.js.map +1 -0
  37. package/dist/diagnostic/zipGenerators.d.ts +9 -0
  38. package/dist/diagnostic/zipGenerators.js +64 -0
  39. package/dist/diagnostic/zipGenerators.js.map +1 -0
  40. package/dist/diagnostic/zipGenerators.test.js +144 -0
  41. package/dist/diagnostic/zipGenerators.test.js.map +1 -0
  42. package/dist/errors.d.ts +2 -1
  43. package/dist/errors.js +3 -1
  44. package/dist/errors.js.map +1 -1
  45. package/dist/interface/config.d.ts +26 -0
  46. package/dist/interface/config.js +3 -0
  47. package/dist/interface/config.js.map +1 -0
  48. package/dist/interface/download.d.ts +2 -2
  49. package/dist/interface/events.d.ts +60 -20
  50. package/dist/interface/events.js +11 -1
  51. package/dist/interface/events.js.map +1 -1
  52. package/dist/interface/httpClient.d.ts +0 -14
  53. package/dist/interface/index.d.ts +8 -4
  54. package/dist/interface/index.js +2 -1
  55. package/dist/interface/index.js.map +1 -1
  56. package/dist/interface/nodes.d.ts +9 -0
  57. package/dist/interface/nodes.js.map +1 -1
  58. package/dist/interface/sharing.d.ts +1 -0
  59. package/dist/interface/upload.d.ts +6 -0
  60. package/dist/internal/download/apiService.js +32 -31
  61. package/dist/internal/download/apiService.js.map +1 -1
  62. package/dist/internal/download/fileDownloader.d.ts +2 -2
  63. package/dist/internal/download/fileDownloader.js.map +1 -1
  64. package/dist/internal/events/apiService.d.ts +4 -6
  65. package/dist/internal/events/apiService.js +15 -22
  66. package/dist/internal/events/apiService.js.map +1 -1
  67. package/dist/internal/events/coreEventManager.d.ts +7 -10
  68. package/dist/internal/events/coreEventManager.js +19 -36
  69. package/dist/internal/events/coreEventManager.js.map +1 -1
  70. package/dist/internal/events/coreEventManager.test.js +87 -0
  71. package/dist/internal/events/coreEventManager.test.js.map +1 -0
  72. package/dist/internal/events/eventManager.d.ts +11 -36
  73. package/dist/internal/events/eventManager.js +59 -105
  74. package/dist/internal/events/eventManager.js.map +1 -1
  75. package/dist/internal/events/eventManager.test.js +167 -82
  76. package/dist/internal/events/eventManager.test.js.map +1 -1
  77. package/dist/internal/events/index.d.ts +13 -33
  78. package/dist/internal/events/index.js +56 -72
  79. package/dist/internal/events/index.js.map +1 -1
  80. package/dist/internal/events/interface.d.ts +59 -14
  81. package/dist/internal/events/interface.js +13 -3
  82. package/dist/internal/events/interface.js.map +1 -1
  83. package/dist/internal/events/volumeEventManager.d.ts +7 -17
  84. package/dist/internal/events/volumeEventManager.js +58 -45
  85. package/dist/internal/events/volumeEventManager.js.map +1 -1
  86. package/dist/internal/events/volumeEventManager.test.d.ts +1 -0
  87. package/dist/internal/events/volumeEventManager.test.js +203 -0
  88. package/dist/internal/events/volumeEventManager.test.js.map +1 -0
  89. package/dist/internal/nodes/cache.d.ts +10 -1
  90. package/dist/internal/nodes/cache.js +17 -0
  91. package/dist/internal/nodes/cache.js.map +1 -1
  92. package/dist/internal/nodes/cryptoService.d.ts +1 -1
  93. package/dist/internal/nodes/cryptoService.js.map +1 -1
  94. package/dist/internal/nodes/events.d.ts +7 -83
  95. package/dist/internal/nodes/events.js +43 -217
  96. package/dist/internal/nodes/events.js.map +1 -1
  97. package/dist/internal/nodes/events.test.js +27 -277
  98. package/dist/internal/nodes/events.test.js.map +1 -1
  99. package/dist/internal/nodes/index.d.ts +3 -4
  100. package/dist/internal/nodes/index.js +5 -5
  101. package/dist/internal/nodes/index.js.map +1 -1
  102. package/dist/internal/nodes/nodesAccess.d.ts +15 -0
  103. package/dist/internal/nodes/nodesAccess.js +37 -0
  104. package/dist/internal/nodes/nodesAccess.js.map +1 -1
  105. package/dist/internal/nodes/nodesAccess.test.js +131 -93
  106. package/dist/internal/nodes/nodesAccess.test.js.map +1 -1
  107. package/dist/internal/nodes/nodesManagement.d.ts +1 -3
  108. package/dist/internal/nodes/nodesManagement.js +12 -26
  109. package/dist/internal/nodes/nodesManagement.js.map +1 -1
  110. package/dist/internal/nodes/nodesManagement.test.js +35 -14
  111. package/dist/internal/nodes/nodesManagement.test.js.map +1 -1
  112. package/dist/internal/shares/cache.d.ts +2 -0
  113. package/dist/internal/shares/cache.js +2 -0
  114. package/dist/internal/shares/cache.js.map +1 -1
  115. package/dist/internal/shares/manager.d.ts +1 -0
  116. package/dist/internal/shares/manager.js +3 -0
  117. package/dist/internal/shares/manager.js.map +1 -1
  118. package/dist/internal/sharing/apiService.js +1 -0
  119. package/dist/internal/sharing/apiService.js.map +1 -1
  120. package/dist/internal/sharing/cryptoService.js +1 -0
  121. package/dist/internal/sharing/cryptoService.js.map +1 -1
  122. package/dist/internal/sharing/events.d.ts +23 -55
  123. package/dist/internal/sharing/events.js +46 -138
  124. package/dist/internal/sharing/events.js.map +1 -1
  125. package/dist/internal/sharing/events.test.js +77 -180
  126. package/dist/internal/sharing/events.test.js.map +1 -1
  127. package/dist/internal/sharing/index.d.ts +4 -5
  128. package/dist/internal/sharing/index.js +5 -5
  129. package/dist/internal/sharing/index.js.map +1 -1
  130. package/dist/internal/sharing/interface.d.ts +3 -0
  131. package/dist/internal/sharing/sharingManagement.d.ts +2 -3
  132. package/dist/internal/sharing/sharingManagement.js +7 -9
  133. package/dist/internal/sharing/sharingManagement.js.map +1 -1
  134. package/dist/internal/sharing/sharingManagement.test.js +9 -39
  135. package/dist/internal/sharing/sharingManagement.test.js.map +1 -1
  136. package/dist/internal/upload/apiService.d.ts +2 -3
  137. package/dist/internal/upload/apiService.js +7 -4
  138. package/dist/internal/upload/apiService.js.map +1 -1
  139. package/dist/internal/upload/index.d.ts +2 -2
  140. package/dist/internal/upload/index.js +3 -3
  141. package/dist/internal/upload/index.js.map +1 -1
  142. package/dist/internal/upload/interface.d.ts +2 -0
  143. package/dist/internal/upload/manager.d.ts +5 -5
  144. package/dist/internal/upload/manager.js +19 -50
  145. package/dist/internal/upload/manager.js.map +1 -1
  146. package/dist/internal/upload/manager.test.js +68 -44
  147. package/dist/internal/upload/manager.test.js.map +1 -1
  148. package/dist/internal/upload/streamUploader.js +1 -2
  149. package/dist/internal/upload/streamUploader.js.map +1 -1
  150. package/dist/internal/upload/streamUploader.test.js +1 -1
  151. package/dist/internal/upload/streamUploader.test.js.map +1 -1
  152. package/dist/protonDriveClient.d.ts +19 -162
  153. package/dist/protonDriveClient.js +26 -190
  154. package/dist/protonDriveClient.js.map +1 -1
  155. package/dist/protonDrivePhotosClient.js +3 -2
  156. package/dist/protonDrivePhotosClient.js.map +1 -1
  157. package/package.json +3 -3
  158. package/src/cache/index.ts +1 -0
  159. package/src/cache/memoryCache.ts +1 -1
  160. package/src/cache/nullCache.ts +38 -0
  161. package/src/config.ts +17 -2
  162. package/src/crypto/openPGPCrypto.ts +2 -0
  163. package/src/diagnostic/eventsGenerator.ts +48 -0
  164. package/src/diagnostic/httpClient.ts +80 -0
  165. package/src/diagnostic/index.ts +38 -0
  166. package/src/diagnostic/integrityVerificationStream.ts +56 -0
  167. package/src/diagnostic/interface.ts +158 -0
  168. package/src/diagnostic/sdkDiagnostic.ts +238 -0
  169. package/src/diagnostic/sdkDiagnosticFull.ts +40 -0
  170. package/src/diagnostic/telemetry.ts +71 -0
  171. package/src/diagnostic/zipGenerators.test.ts +177 -0
  172. package/src/diagnostic/zipGenerators.ts +70 -0
  173. package/src/errors.ts +4 -1
  174. package/src/interface/config.ts +28 -0
  175. package/src/interface/download.ts +2 -2
  176. package/src/interface/events.ts +66 -21
  177. package/src/interface/httpClient.ts +0 -16
  178. package/src/interface/index.ts +8 -4
  179. package/src/interface/nodes.ts +21 -12
  180. package/src/interface/sharing.ts +1 -0
  181. package/src/interface/upload.ts +6 -0
  182. package/src/internal/download/apiService.ts +11 -8
  183. package/src/internal/download/fileDownloader.ts +2 -2
  184. package/src/internal/events/apiService.ts +25 -28
  185. package/src/internal/events/coreEventManager.test.ts +101 -0
  186. package/src/internal/events/coreEventManager.ts +20 -45
  187. package/src/internal/events/eventManager.test.ts +201 -88
  188. package/src/internal/events/eventManager.ts +69 -115
  189. package/src/internal/events/index.ts +54 -84
  190. package/src/internal/events/interface.ts +70 -15
  191. package/src/internal/events/volumeEventManager.test.ts +243 -0
  192. package/src/internal/events/volumeEventManager.ts +55 -53
  193. package/src/internal/nodes/cache.ts +20 -2
  194. package/src/internal/nodes/cryptoService.ts +1 -1
  195. package/src/internal/nodes/events.test.ts +29 -335
  196. package/src/internal/nodes/events.ts +45 -253
  197. package/src/internal/nodes/index.ts +6 -8
  198. package/src/internal/nodes/interface.ts +2 -2
  199. package/src/internal/nodes/nodesAccess.test.ts +132 -91
  200. package/src/internal/nodes/nodesAccess.ts +40 -1
  201. package/src/internal/nodes/nodesManagement.test.ts +39 -15
  202. package/src/internal/nodes/nodesManagement.ts +12 -30
  203. package/src/internal/shares/cache.ts +4 -2
  204. package/src/internal/shares/manager.ts +9 -5
  205. package/src/internal/sharing/apiService.ts +1 -0
  206. package/src/internal/sharing/cache.ts +1 -1
  207. package/src/internal/sharing/cryptoService.ts +1 -0
  208. package/src/internal/sharing/events.test.ts +89 -195
  209. package/src/internal/sharing/events.ts +42 -156
  210. package/src/internal/sharing/index.ts +6 -9
  211. package/src/internal/sharing/interface.ts +6 -2
  212. package/src/internal/sharing/sharingManagement.test.ts +10 -40
  213. package/src/internal/sharing/sharingManagement.ts +7 -11
  214. package/src/internal/upload/apiService.ts +5 -6
  215. package/src/internal/upload/index.ts +5 -5
  216. package/src/internal/upload/interface.ts +2 -0
  217. package/src/internal/upload/manager.test.ts +75 -45
  218. package/src/internal/upload/manager.ts +24 -54
  219. package/src/internal/upload/streamUploader.test.ts +0 -1
  220. package/src/internal/upload/streamUploader.ts +0 -2
  221. package/src/protonDriveClient.ts +75 -244
  222. package/src/protonDrivePhotosClient.ts +4 -3
  223. package/dist/internal/events/cache.d.ts +0 -28
  224. package/dist/internal/events/cache.js +0 -67
  225. package/dist/internal/events/cache.js.map +0 -1
  226. package/dist/internal/events/cache.test.js +0 -43
  227. package/dist/internal/events/cache.test.js.map +0 -1
  228. package/dist/internal/nodes/index.test.js +0 -114
  229. package/dist/internal/nodes/index.test.js.map +0 -1
  230. package/src/internal/events/cache.test.ts +0 -47
  231. package/src/internal/events/cache.ts +0 -80
  232. package/src/internal/nodes/index.test.ts +0 -137
  233. /package/dist/{internal/events/cache.test.d.ts → diagnostic/zipGenerators.test.d.ts} +0 -0
  234. /package/dist/internal/{nodes/index.test.d.ts → events/coreEventManager.test.d.ts} +0 -0
@@ -0,0 +1,177 @@
1
+ import { zipGenerators } from './zipGenerators';
2
+
3
+ async function* createTimedGenerator<T>(values: { value: T; delay: number }[]): AsyncGenerator<T> {
4
+ for (const { value, delay } of values) {
5
+ await new Promise(resolve => setTimeout(resolve, delay));
6
+ yield value;
7
+ }
8
+ }
9
+
10
+ async function* createEmptyGenerator<T>(): AsyncGenerator<T> {
11
+ return;
12
+ }
13
+
14
+ describe('zipGenerators', () => {
15
+ it('should handle both generators being empty', async () => {
16
+ const genA = createEmptyGenerator<string>();
17
+ const genB = createEmptyGenerator<number>();
18
+
19
+ const result: (string | number)[] = [];
20
+ const zipGen = zipGenerators(genA, genB);
21
+
22
+ for await (const value of zipGen) {
23
+ result.push(value);
24
+ }
25
+
26
+ expect(result).toEqual([]);
27
+ });
28
+
29
+ it('should handle one generator being empty (first empty)', async () => {
30
+ const genA = createEmptyGenerator<string>();
31
+ const genB = createTimedGenerator([
32
+ { value: 1, delay: 10 },
33
+ { value: 2, delay: 10 },
34
+ ]);
35
+
36
+ const result: (string | number)[] = [];
37
+ const zipGen = zipGenerators(genA, genB);
38
+
39
+ const promise = (async () => {
40
+ for await (const value of zipGen) {
41
+ result.push(value);
42
+ }
43
+ })();
44
+
45
+ await promise;
46
+
47
+ expect(result).toEqual([1, 2]);
48
+ });
49
+
50
+ it('should handle one generator being empty (second empty)', async () => {
51
+ const genA = createTimedGenerator([
52
+ { value: 'a', delay: 10 },
53
+ { value: 'b', delay: 10 },
54
+ ]);
55
+ const genB = createEmptyGenerator<number>();
56
+
57
+ const result: (string | number)[] = [];
58
+ const zipGen = zipGenerators(genA, genB);
59
+
60
+ const promise = (async () => {
61
+ for await (const value of zipGen) {
62
+ result.push(value);
63
+ }
64
+ })();
65
+
66
+ await promise;
67
+
68
+ expect(result).toEqual(['a', 'b']);
69
+ });
70
+
71
+ it('should handle both generators with same number of elements yielded at same time', async () => {
72
+ const genA = createTimedGenerator([
73
+ { value: 'a1', delay: 10 },
74
+ { value: 'a2', delay: 10 },
75
+ { value: 'a3', delay: 10 },
76
+ ]);
77
+ const genB = createTimedGenerator([
78
+ { value: 'b1', delay: 10 },
79
+ { value: 'b2', delay: 10 },
80
+ { value: 'b3', delay: 10 },
81
+ ]);
82
+
83
+ const result: string[] = [];
84
+ const zipGen = zipGenerators(genA, genB);
85
+
86
+ const promise = (async () => {
87
+ for await (const value of zipGen) {
88
+ result.push(value);
89
+ }
90
+ })();
91
+
92
+ await promise;
93
+
94
+ // Since they yield at the same time, the order depends on Promise.race behavior
95
+ // Both values should be present, but order may vary
96
+ expect(result).toHaveLength(6);
97
+ expect(result).toEqual(expect.arrayContaining(['a1', 'a2', 'a3', 'b1', 'b2', 'b3']));
98
+ });
99
+
100
+ it('should handle generators with different timing - first generator faster', async () => {
101
+ const genA = createTimedGenerator([
102
+ { value: 'fast1', delay: 10 },
103
+ { value: 'fast2', delay: 10 },
104
+ { value: 'fast3', delay: 10 },
105
+ ]);
106
+ const genB = createTimedGenerator([
107
+ { value: 'slow1', delay: 50 },
108
+ { value: 'slow2', delay: 50 },
109
+ ]);
110
+
111
+ const result: string[] = [];
112
+ const zipGen = zipGenerators(genA, genB);
113
+
114
+ const promise = (async () => {
115
+ for await (const value of zipGen) {
116
+ result.push(value);
117
+ }
118
+ })();
119
+
120
+ await promise;
121
+
122
+ expect(result).toEqual(['fast1', 'fast2', 'fast3', 'slow1', 'slow2']);
123
+ });
124
+
125
+ it('should handle generators with different timing - second generator faster', async () => {
126
+ const genA = createTimedGenerator([
127
+ { value: 'slow1', delay: 50 },
128
+ { value: 'slow2', delay: 50 },
129
+ ]);
130
+ const genB = createTimedGenerator([
131
+ { value: 'fast1', delay: 10 },
132
+ { value: 'fast2', delay: 10 },
133
+ { value: 'fast3', delay: 10 },
134
+ ]);
135
+
136
+ const result: string[] = [];
137
+ const zipGen = zipGenerators(genA, genB);
138
+
139
+ const promise = (async () => {
140
+ for await (const value of zipGen) {
141
+ result.push(value);
142
+ }
143
+ })();
144
+
145
+ await promise;
146
+
147
+ expect(result).toEqual(['fast1', 'fast2', 'fast3', 'slow1', 'slow2']);
148
+ });
149
+
150
+ it('should handle mixed timing with overlapping yields', async () => {
151
+ const genA = createTimedGenerator([
152
+ { value: 'A1', delay: 50 },
153
+ { value: 'A2', delay: 100 },
154
+ { value: 'A3', delay: 100 },
155
+ ]);
156
+ const genB = createTimedGenerator([
157
+ { value: 'B1', delay: 100 },
158
+ { value: 'B2', delay: 100 },
159
+ { value: 'B3', delay: 200 },
160
+ ]);
161
+
162
+ const result: string[] = [];
163
+ const timestamps: number[] = [];
164
+ const zipGen = zipGenerators(genA, genB);
165
+
166
+ const promise = (async () => {
167
+ for await (const value of zipGen) {
168
+ result.push(value);
169
+ timestamps.push(Date.now());
170
+ }
171
+ })();
172
+
173
+ await promise;
174
+
175
+ expect(result).toEqual(['A1', 'B1', 'A2', 'B2', 'A3', 'B3']);
176
+ });
177
+ });
@@ -0,0 +1,70 @@
1
+ /**
2
+ * Zips two generators into one.
3
+ *
4
+ * The combined generator yields values from both generators in the order they
5
+ * are produced.
6
+ */
7
+ export async function* zipGenerators<T, U>(
8
+ genA: AsyncGenerator<T>,
9
+ genB: AsyncGenerator<U>,
10
+ options?: {
11
+ stopOnFirstDone?: boolean
12
+ },
13
+ ): AsyncGenerator<T | U> {
14
+ const { stopOnFirstDone = false } = options || {};
15
+
16
+ const itA = genA[Symbol.asyncIterator]();
17
+ const itB = genB[Symbol.asyncIterator]();
18
+
19
+ let promiseA: Promise<IteratorResult<T>> | undefined = itA.next();
20
+ let promiseB: Promise<IteratorResult<U>> | undefined = itB.next();
21
+
22
+ while (promiseA && promiseB) {
23
+ const result = await Promise.race([
24
+ promiseA.then(res => ({ source: 'A' as const, result: res })),
25
+ promiseB.then(res => ({ source: 'B' as const, result: res }))
26
+ ]);
27
+
28
+ if (result.source === 'A') {
29
+ if (result.result.done) {
30
+ promiseA = undefined;
31
+ if (stopOnFirstDone) {
32
+ break;
33
+ }
34
+ } else {
35
+ yield result.result.value;
36
+ promiseA = itA.next();
37
+ }
38
+ } else {
39
+ if (result.result.done) {
40
+ promiseB = undefined;
41
+ if (stopOnFirstDone) {
42
+ break;
43
+ }
44
+ } else {
45
+ yield result.result.value;
46
+ promiseB = itB.next();
47
+ }
48
+ }
49
+ }
50
+
51
+ if (stopOnFirstDone) {
52
+ return;
53
+ }
54
+
55
+ if (promiseA) {
56
+ const result = await promiseA;
57
+ if (!result.done) {
58
+ yield result.value;
59
+ }
60
+ yield* itA;
61
+ }
62
+
63
+ if (promiseB) {
64
+ const result = await promiseB;
65
+ if (!result.done) {
66
+ yield result.value;
67
+ }
68
+ yield* itB;
69
+ }
70
+ }
package/src/errors.ts CHANGED
@@ -75,9 +75,12 @@ export class NodeAlreadyExistsValidationError extends ValidationError {
75
75
 
76
76
  public readonly existingNodeUid?: string;
77
77
 
78
- constructor(message: string, code: number, existingNodeUid?: string) {
78
+ public readonly ongoingUploadByOtherClient: boolean;
79
+
80
+ constructor(message: string, code: number, existingNodeUid?: string, ongoingUploadByOtherClient = false) {
79
81
  super(message, code);
80
82
  this.existingNodeUid = existingNodeUid;
83
+ this.ongoingUploadByOtherClient = ongoingUploadByOtherClient;
81
84
  }
82
85
  }
83
86
 
@@ -0,0 +1,28 @@
1
+ export type ProtonDriveConfig = {
2
+ /**
3
+ * The base URL for the Proton Drive (without schema).
4
+ *
5
+ * If not provided, defaults to 'drive-api.proton.me'.
6
+ */
7
+ baseUrl?: string,
8
+
9
+ /**
10
+ * The language to use for error messages.
11
+ *
12
+ * If not provided, defaults to 'en'.
13
+ */
14
+ language?: string,
15
+
16
+ /**
17
+ * Client UID is used to identify the client for the upload.
18
+ *
19
+ * If the upload failed because of the existing draft, the SDK will
20
+ * automatically clean up the existing draft and start a new upload.
21
+ * If the client UID doesn't match, the SDK throws and then you need
22
+ * to explicitely ask the user to override the existing draft.
23
+ *
24
+ * You can force the upload by setting up
25
+ * `overrideExistingDraftByOtherClient` to true.
26
+ */
27
+ clientUid?: string,
28
+ }
@@ -15,14 +15,14 @@ export interface FileDownloader {
15
15
  *
16
16
  * @param onProgress - Callback that is called with the number of downloaded bytes
17
17
  */
18
- writeToStream(streamFactory: WritableStream, onProgress: (downloadedBytes: number) => void): DownloadController,
18
+ writeToStream(streamFactory: WritableStream, onProgress?: (downloadedBytes: number) => void): DownloadController,
19
19
 
20
20
  /**
21
21
  * Same as `writeToStream` but without verification checks.
22
22
  *
23
23
  * Use this only for debugging purposes.
24
24
  */
25
- unsafeWriteToStream(streamFactory: WritableStream, onProgress: (downloadedBytes: number) => void): DownloadController,
25
+ unsafeWriteToStream(streamFactory: WritableStream, onProgress?: (downloadedBytes: number) => void): DownloadController,
26
26
  }
27
27
 
28
28
  export interface DownloadController {
@@ -1,30 +1,75 @@
1
- import { Device } from './devices';
2
- import { MaybeNode } from './nodes';
1
+ export enum SDKEvent {
2
+ TransfersPaused = "transfersPaused",
3
+ TransfersResumed = "transfersResumed",
4
+ RequestsThrottled = "requestsThrottled",
5
+ RequestsUnthrottled = "requestsUnthrottled",
6
+ }
3
7
 
4
- export type DeviceEventCallback = (deviceEvent: DeviceEvent) => void;
5
- export type NodeEventCallback = (nodeEvent: NodeEvent) => void;
8
+ export interface LatestEventIdProvider {
9
+ getLatestEventId(treeEventScopeId: string): string | null;
10
+ }
11
+
12
+ /**
13
+ * Callback that accepts list of Drive events and flag whether no
14
+ * event should be processed, but rather full cache refresh should be
15
+ * performed.
16
+ *
17
+ * Drive listeners should never throw and be wrapped in a try-catch loop.
18
+ *
19
+ * @param fullRefreshVolumeId - ID of the volume that should be fully refreshed.
20
+ */
21
+ export type DriveListener = (event: DriveEvent) => Promise<void>;
22
+
23
+ type NodeCruEventType = DriveEventType.NodeCreated | DriveEventType.NodeUpdated;
6
24
 
7
25
  export type NodeEvent = {
8
- type: 'update',
9
- uid: string,
10
- node: MaybeNode,
26
+ type: NodeCruEventType,
27
+ nodeUid: string,
28
+ parentNodeUid?: string,
29
+ isTrashed: boolean,
30
+ isShared: boolean,
31
+ treeEventScopeId: string,
32
+ eventId: string,
11
33
  } | {
12
- type: 'remove',
13
- uid: string,
34
+ type: DriveEventType.NodeDeleted,
35
+ nodeUid: string,
36
+ parentNodeUid?: string,
37
+ treeEventScopeId: string,
38
+ eventId: string,
14
39
  }
15
40
 
16
- export type DeviceEvent = {
17
- type: 'update',
18
- uid: string,
19
- device: Device,
20
- } | {
21
- type: 'remove',
22
- uid: string,
41
+ export type FastForwardEvent = {
42
+ type: DriveEventType.FastForward,
43
+ treeEventScopeId: string,
44
+ eventId: string,
23
45
  }
24
46
 
25
- export enum SDKEvent {
26
- TransfersPaused = "transfersPaused",
27
- TransfersResumed = "transfersResumed",
28
- RequestsThrottled = "requestsThrottled",
29
- RequestsUnthrottled = "requestsUnthrottled",
47
+ export type TreeRefreshEvent = {
48
+ type: DriveEventType.TreeRefresh,
49
+ treeEventScopeId: string,
50
+ eventId: string,
51
+ }
52
+
53
+ export type TreeRemovalEvent = {
54
+ type: DriveEventType.TreeRemove,
55
+ treeEventScopeId: string,
56
+ eventId: 'none',
57
+ }
58
+
59
+ export type SharedWithMeUpdated = {
60
+ type: DriveEventType.SharedWithMeUpdated,
61
+ eventId: string,
62
+ treeEventScopeId: 'core',
63
+ }
64
+
65
+ export type DriveEvent = NodeEvent | FastForwardEvent | TreeRefreshEvent | TreeRemovalEvent | FastForwardEvent | SharedWithMeUpdated;
66
+
67
+ export enum DriveEventType {
68
+ NodeCreated = 'node_created',
69
+ NodeUpdated = 'node_updated',
70
+ NodeDeleted = 'node_deleted',
71
+ SharedWithMeUpdated = 'shared_with_me_updated',
72
+ TreeRefresh = 'tree_refresh',
73
+ TreeRemove = 'tree_remove',
74
+ FastForward = 'fast_forward'
30
75
  }
@@ -24,19 +24,3 @@ type ProtonDriveHTTPClientBaseOptions = {
24
24
  timeoutMs: number,
25
25
  signal?: AbortSignal,
26
26
  }
27
-
28
- export type ProtonDriveConfig = {
29
- /**
30
- * The base URL for the Proton Drive (without schema).
31
- *
32
- * If not provided, defaults to 'drive-api.proton.me'.
33
- */
34
- baseUrl?: string,
35
-
36
- /**
37
- * The language to use for error messages.
38
- *
39
- * If not provided, defaults to 'en'.
40
- */
41
- language?: string,
42
- }
@@ -1,19 +1,22 @@
1
1
  import { ProtonDriveCache } from '../cache';
2
2
  import { OpenPGPCrypto, PrivateKey, SessionKey, SRPModule } from '../crypto';
3
+ import { LatestEventIdProvider } from '../internal/events/interface';
3
4
  import { ProtonDriveAccount } from './account';
4
- import { ProtonDriveHTTPClient, ProtonDriveConfig } from './httpClient';
5
+ import { ProtonDriveConfig } from './config';
6
+ import { ProtonDriveHTTPClient } from './httpClient';
5
7
  import { Telemetry, MetricEvent } from './telemetry';
6
8
 
7
9
  export type { Result } from './result';
8
10
  export { resultOk, resultError } from './result';
9
11
  export type { ProtonDriveAccount, ProtonDriveAccountAddress } from './account';
10
12
  export type { Author, UnverifiedAuthorError, AnonymousUser } from './author';
13
+ export type { ProtonDriveConfig } from './config';
11
14
  export type { Device, DeviceOrUid } from './devices';
12
15
  export { DeviceType } from './devices';
13
16
  export type { FileDownloader, DownloadController } from './download';
14
- export type { NodeEvent, DeviceEvent, DeviceEventCallback, NodeEventCallback } from './events';
15
- export { SDKEvent } from './events';
16
- export type { ProtonDriveHTTPClient, ProtonDriveHTTPClientJsonOptions, ProtonDriveHTTPClientBlobOptions, ProtonDriveConfig } from './httpClient';
17
+ export type { DriveListener, LatestEventIdProvider, DriveEvent } from './events';
18
+ export { DriveEventType, SDKEvent } from './events';
19
+ export type { ProtonDriveHTTPClient, ProtonDriveHTTPClientJsonOptions, ProtonDriveHTTPClientBlobOptions } from './httpClient';
17
20
  export type { MaybeNode, NodeEntity, DegradedNode, MaybeMissingNode, MissingNode, InvalidNameError, Revision, NodeOrUid, RevisionOrUid, NodeResult } from './nodes';
18
21
  export { NodeType, MemberRole, RevisionState } from './nodes';
19
22
  export type { ProtonInvitation, ProtonInvitationWithNode, NonProtonInvitation, Member, PublicLink, MaybeBookmark, Bookmark, DegradedBookmark, ProtonInvitationOrUid, NonProtonInvitationOrUid, BookmarkOrUid, ShareNodeSettings, UnshareNodeSettings, ShareMembersSettings, SharePublicLinkSettings, SharePublicLinkSettingsObject, ShareResult } from './sharing';
@@ -43,4 +46,5 @@ export interface ProtonDriveClientContructorParameters {
43
46
  srpModule: SRPModule,
44
47
  config?: ProtonDriveConfig,
45
48
  telemetry?: ProtonDriveTelemetry,
49
+ latestEventIdProvider?: LatestEventIdProvider
46
50
  };
@@ -3,7 +3,7 @@ import { Author } from './author';
3
3
 
4
4
  /**
5
5
  * Node representing a file or folder in the system.
6
- *
6
+ *
7
7
  * This covers both happy path and degraded path. It is used in the SDK to
8
8
  * represent the node in a way that is easy to work with. Whenever any field
9
9
  * cannot be decrypted, it is returned as `DegradedNode` type.
@@ -12,7 +12,7 @@ export type MaybeNode = Result<NodeEntity, DegradedNode>;
12
12
 
13
13
  /**
14
14
  * Node representing a file or folder in the system, or missing node.
15
- *
15
+ *
16
16
  * In most cases, SDK returns `MaybeNode`, but in some specific cases, when
17
17
  * client is requesting specific nodes, SDK must return `MissingNode` type
18
18
  * to indicate the case when the node is not available. That can be when
@@ -27,11 +27,11 @@ export type MissingNode = {
27
27
 
28
28
  /**
29
29
  * Node representing a file or folder in the system.
30
- *
30
+ *
31
31
  * This is a happy path representation of the node. It is used in the SDK to
32
32
  * represent the node in a way that is easy to work with. Whenever any field
33
33
  * cannot be decrypted, it is returned as `DegradedNode` type.
34
- *
34
+ *
35
35
  * SDK never returns this entity directly but wrapped in `MaybeNode`.
36
36
  *
37
37
  * Note on naming: Node is reserved by JS/DOM, thus we need exception how the
@@ -43,7 +43,7 @@ export type NodeEntity = {
43
43
  name: string,
44
44
  /**
45
45
  * Author of the node key.
46
- *
46
+ *
47
47
  * Person who created the node and keys for it. If user A uploads the file
48
48
  * and user B renames the file and uploads new revision, name and content
49
49
  * author is user B, while key author stays to user A who has forever
@@ -52,7 +52,7 @@ export type NodeEntity = {
52
52
  keyAuthor: Author,
53
53
  /**
54
54
  * Author of the name.
55
- *
55
+ *
56
56
  * Person who named the file. If user A uploads the file and user B renames
57
57
  * the file, key and content author is user A, while name author is user B.
58
58
  */
@@ -87,20 +87,29 @@ export type NodeEntity = {
87
87
  folder?: {
88
88
  claimedModificationTime?: Date,
89
89
  },
90
+ /**
91
+ * Provides an ID for the event scope.
92
+ *
93
+ * By subscribing to events in a scope, all updates to nodes
94
+ * withing that scope will be passed to the client. The scope can
95
+ * comprise one or more folder trees and will be shared by all
96
+ * nodes in the tree. Nodes cannot change scopes.
97
+ */
98
+ treeEventScopeId: string,
90
99
  }
91
100
 
92
101
  /**
93
102
  * Degraded node representing a file or folder in the system.
94
- *
103
+ *
95
104
  * This is a degraded path representation of the node. It is used in the SDK to
96
105
  * represent the node in a way that is easy to work with. Whenever any field
97
106
  * cannot be decrypted, it is returned as `DegradedNode` type.
98
- *
107
+ *
99
108
  * SDK never returns this entity directly but wrapped in `MaybeNode`.
100
- *
109
+ *
101
110
  * The node can be still used around, but it is not guaranteed that all
102
111
  * properties are decrypted, or that all actions can be performed on it.
103
- *
112
+ *
104
113
  * For example, if the node has issue decrypting the name, the name will be
105
114
  * set as `Error` and potentially rename or move actions will not be
106
115
  * possible, but download and upload new revision will still work.
@@ -110,10 +119,10 @@ export type DegradedNode = Omit<NodeEntity, 'name' | 'activeRevision'> & {
110
119
  activeRevision?: Result<Revision, Error>,
111
120
  /**
112
121
  * If the error is not related to any specific field, it is set here.
113
- *
122
+ *
114
123
  * For example, if the node has issue decrypting the name, the name will be
115
124
  * set as `Error` while this will be empty.
116
- *
125
+ *
117
126
  * On the other hand, if the node has issue decrypting the node key, but
118
127
  * the name is still working, this will include the node key error, while
119
128
  * the name will be set to the decrypted value.
@@ -36,6 +36,7 @@ export type PublicLink = {
36
36
  url: string,
37
37
  customPassword?: string,
38
38
  expirationTime?: Date,
39
+ numberOfInitializedDownloads: number
39
40
  }
40
41
 
41
42
  /**
@@ -24,6 +24,12 @@ export type UploadMetadata = {
24
24
  * The metadata will be encrypted and stored with the file.
25
25
  */
26
26
  additionalMetadata?: object,
27
+ /**
28
+ * If there is an existing draft by another client, the upload will be
29
+ * rejected. If user decides to override the existing draft and continue
30
+ * with the upload, set this to true.
31
+ */
32
+ overrideExistingDraftByOtherClient?: boolean,
27
33
  };
28
34
 
29
35
  export interface FileRevisionUploader {
@@ -1,5 +1,3 @@
1
- import { c } from "ttag";
2
- import { ValidationError } from "../../errors";
3
1
  import { DriveAPIService, drivePaths, ObserverStream } from "../apiService";
4
2
  import { makeNodeThumbnailUid, splitNodeRevisionUid, splitNodeThumbnailUid } from "../uids";
5
3
  import { BlockMetadata } from "./interface";
@@ -85,19 +83,22 @@ export class DownloadAPIService {
85
83
  return encryptedBlock;
86
84
  }
87
85
 
88
- // Improvement requested: support multiple volumes.
89
86
  async* iterateThumbnails(thumbnailUids: string[], signal?: AbortSignal): AsyncGenerator<
90
87
  { uid: string, ok: true, bareUrl: string, token: string } |
91
88
  { uid: string, ok: false, error: string }
92
89
  > {
93
- const thumbnailIds = thumbnailUids.map(splitNodeThumbnailUid);
90
+ const splitedThumbnailsIds = thumbnailUids.map(splitNodeThumbnailUid);
94
91
 
95
- const uniqueVolumeIds = new Set(thumbnailIds.map(({ volumeId }) => volumeId));
96
- if (uniqueVolumeIds.size !== 1) {
97
- throw new ValidationError(c('Error').t`Loading thumbnails from multiple sections is not allowed`);
92
+ const thumbnailIdsByVolumeId = new Map<string, { volumeId: string, thumbnailId: string, nodeId: string }[]>();
93
+ for (const { volumeId, thumbnailId, nodeId } of splitedThumbnailsIds) {
94
+ if (!thumbnailIdsByVolumeId.has(volumeId)) {
95
+ thumbnailIdsByVolumeId.set(volumeId, []);
96
+ }
97
+ thumbnailIdsByVolumeId.get(volumeId)?.push({ volumeId, thumbnailId, nodeId });
98
98
  }
99
- const volumeId = thumbnailIds[0].volumeId;
100
99
 
100
+
101
+ for (const [volumeId, thumbnailIds] of thumbnailIdsByVolumeId.entries()) {
101
102
  const result = await this.apiService.post<PostGetThumbnailsRequest, PostGetThumbnailsResponse>(
102
103
  `drive/volumes/${volumeId}/thumbnails`,
103
104
  {
@@ -130,6 +131,8 @@ export class DownloadAPIService {
130
131
  error: error.Error,
131
132
  };
132
133
  }
134
+
135
+ }
133
136
  }
134
137
  }
135
138
 
@@ -49,7 +49,7 @@ export class FileDownloader {
49
49
  return this.revision.claimedSize;
50
50
  }
51
51
 
52
- writeToStream(stream: WritableStream, onProgress: (downloadedBytes: number) => void): DownloadController {
52
+ writeToStream(stream: WritableStream, onProgress?: (downloadedBytes: number) => void): DownloadController {
53
53
  if (this.controller.promise) {
54
54
  throw new Error(`Download already started`);
55
55
  }
@@ -57,7 +57,7 @@ export class FileDownloader {
57
57
  return this.controller;
58
58
  }
59
59
 
60
- unsafeWriteToStream(stream: WritableStream, onProgress: (downloadedBytes: number) => void): DownloadController {
60
+ unsafeWriteToStream(stream: WritableStream, onProgress?: (downloadedBytes: number) => void): DownloadController {
61
61
  if (this.controller.promise) {
62
62
  throw new Error(`Download already started`);
63
63
  }