@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,243 @@
1
+ import { getMockLogger } from "../../tests/logger";
2
+ import { NotFoundAPIError } from "../apiService";
3
+ import { EventsAPIService } from "./apiService";
4
+ import { VolumeEventManager } from "./volumeEventManager";
5
+ import { DriveEventsListWithStatus, DriveEventType } from "./interface";
6
+
7
+ jest.mock("./apiService");
8
+
9
+ describe("VolumeEventManager", () => {
10
+ let manager: VolumeEventManager;
11
+ let mockEventsAPIService: jest.Mocked<EventsAPIService>;
12
+ const mockLogger = getMockLogger();
13
+ const volumeId = "volumeId123";
14
+
15
+ beforeEach(() => {
16
+ jest.clearAllMocks();
17
+
18
+ mockEventsAPIService = {
19
+ getVolumeLatestEventId: jest.fn(),
20
+ getVolumeEvents: jest.fn(),
21
+ getCoreLatestEventId: jest.fn(),
22
+ getCoreEvents: jest.fn(),
23
+ } as any;
24
+
25
+ manager = new VolumeEventManager(
26
+ mockLogger,
27
+ mockEventsAPIService,
28
+ volumeId
29
+ );
30
+ });
31
+
32
+ describe("getLatestEventId", () => {
33
+ it("should return the latest event ID from API", async () => {
34
+ const expectedEventId = "eventId123";
35
+ mockEventsAPIService.getVolumeLatestEventId.mockResolvedValue(expectedEventId);
36
+
37
+ const result = await manager.getLatestEventId();
38
+
39
+ expect(result).toBe(expectedEventId);
40
+ expect(mockEventsAPIService.getVolumeLatestEventId).toHaveBeenCalledWith(volumeId);
41
+ });
42
+
43
+ it("should throw UnsubscribeFromEventsSourceError when API returns NotFoundAPIError", async () => {
44
+ const notFoundError = new NotFoundAPIError("Event not found", 2501);
45
+ mockEventsAPIService.getVolumeLatestEventId.mockRejectedValue(notFoundError);
46
+
47
+ await expect(manager.getLatestEventId()).rejects.toThrow("Event not found");
48
+ });
49
+
50
+ it("should rethrow other errors", async () => {
51
+ const networkError = new Error("Network error");
52
+ mockEventsAPIService.getVolumeLatestEventId.mockRejectedValue(networkError);
53
+
54
+ await expect(manager.getLatestEventId()).rejects.toThrow("Network error");
55
+ });
56
+ });
57
+
58
+ describe("getEvents", () => {
59
+ it("should yield events from API response", async () => {
60
+ const mockEventsResponse: DriveEventsListWithStatus = {
61
+ latestEventId: "eventId456",
62
+ more: false,
63
+ refresh: false,
64
+ events: [
65
+ {
66
+ type: DriveEventType.NodeCreated,
67
+ nodeUid: "node1",
68
+ parentNodeUid: "parent1",
69
+ isTrashed: false,
70
+ isShared: false,
71
+ treeEventScopeId: volumeId,
72
+ eventId: "eventId456",
73
+ }
74
+ ],
75
+ };
76
+
77
+ mockEventsAPIService.getVolumeEvents.mockResolvedValue(mockEventsResponse);
78
+
79
+ const events = [];
80
+ for await (const event of manager.getEvents("startEventId")) {
81
+ events.push(event);
82
+ }
83
+
84
+ expect(events).toEqual(mockEventsResponse.events);
85
+ expect(mockEventsAPIService.getVolumeEvents).toHaveBeenCalledWith(volumeId, "startEventId");
86
+ });
87
+
88
+ it("should continue fetching when more events are available", async () => {
89
+ const firstResponse: DriveEventsListWithStatus = {
90
+ latestEventId: "eventId2",
91
+ more: true,
92
+ refresh: false,
93
+ events: [
94
+ {
95
+ type: DriveEventType.NodeCreated,
96
+ nodeUid: "node1",
97
+ parentNodeUid: "parent1",
98
+ isTrashed: false,
99
+ isShared: false,
100
+ treeEventScopeId: volumeId,
101
+ eventId: "eventId2",
102
+ }
103
+ ],
104
+ };
105
+
106
+ const secondResponse: DriveEventsListWithStatus = {
107
+ latestEventId: "eventId3",
108
+ more: false,
109
+ refresh: false,
110
+ events: [
111
+ {
112
+ type: DriveEventType.NodeUpdated,
113
+ nodeUid: "node2",
114
+ parentNodeUid: "parent1",
115
+ isTrashed: false,
116
+ isShared: false,
117
+ treeEventScopeId: volumeId,
118
+ eventId: "eventId3",
119
+ }
120
+ ],
121
+ };
122
+
123
+ mockEventsAPIService.getVolumeEvents
124
+ .mockResolvedValueOnce(firstResponse)
125
+ .mockResolvedValueOnce(secondResponse);
126
+
127
+ const events = [];
128
+ for await (const event of manager.getEvents("startEventId")) {
129
+ events.push(event);
130
+ }
131
+
132
+ expect(events).toHaveLength(2);
133
+ expect(events[0]).toEqual(firstResponse.events[0]);
134
+ expect(events[1]).toEqual(secondResponse.events[0]);
135
+ expect(mockEventsAPIService.getVolumeEvents).toHaveBeenCalledTimes(2);
136
+ expect(mockEventsAPIService.getVolumeEvents).toHaveBeenNthCalledWith(1, volumeId, "startEventId");
137
+ expect(mockEventsAPIService.getVolumeEvents).toHaveBeenNthCalledWith(2, volumeId, "eventId2");
138
+ });
139
+
140
+ it("should yield TreeRefresh event when refresh is true", async () => {
141
+ const mockEventsResponse: DriveEventsListWithStatus = {
142
+ latestEventId: "eventId789",
143
+ more: false,
144
+ refresh: true,
145
+ events: [],
146
+ };
147
+
148
+ mockEventsAPIService.getVolumeEvents.mockResolvedValue(mockEventsResponse);
149
+
150
+ const events = [];
151
+ for await (const event of manager.getEvents("startEventId")) {
152
+ events.push(event);
153
+ }
154
+
155
+ expect(events).toHaveLength(1);
156
+ expect(events[0]).toEqual({
157
+ type: DriveEventType.TreeRefresh,
158
+ treeEventScopeId: volumeId,
159
+ eventId: "eventId789",
160
+ });
161
+ });
162
+
163
+ it("should yield FastForward event when no events but eventId changed", async () => {
164
+ const mockEventsResponse: DriveEventsListWithStatus = {
165
+ latestEventId: "newEventId",
166
+ more: false,
167
+ refresh: false,
168
+ events: [],
169
+ };
170
+
171
+ mockEventsAPIService.getVolumeEvents.mockResolvedValue(mockEventsResponse);
172
+
173
+ const events = [];
174
+ for await (const event of manager.getEvents("oldEventId")) {
175
+ events.push(event);
176
+ }
177
+
178
+ expect(events).toHaveLength(1);
179
+ expect(events[0]).toEqual({
180
+ type: DriveEventType.FastForward,
181
+ treeEventScopeId: volumeId,
182
+ eventId: "newEventId",
183
+ });
184
+ });
185
+
186
+ it("should yield TreeRemove event when API returns NotFoundAPIError", async () => {
187
+ const notFoundError = new NotFoundAPIError("Volume not found", 2501);
188
+ mockEventsAPIService.getVolumeEvents.mockRejectedValue(notFoundError);
189
+
190
+ const events = [];
191
+ try {
192
+ for await (const event of manager.getEvents("startEventId")) {
193
+ events.push(event);
194
+ }
195
+ } catch (error) {
196
+ // The error should be re-thrown, but first it should yield a TreeRemove event
197
+ expect(error).toBe(notFoundError);
198
+ }
199
+
200
+ expect(events).toHaveLength(1);
201
+ expect(events[0]).toEqual({
202
+ type: DriveEventType.TreeRemove,
203
+ treeEventScopeId: volumeId,
204
+ eventId: 'none',
205
+ });
206
+ });
207
+
208
+ it("should rethrow non-NotFoundAPIError errors", async () => {
209
+ const networkError = new Error("Network error");
210
+ mockEventsAPIService.getVolumeEvents.mockRejectedValue(networkError);
211
+
212
+ const eventGenerator = manager.getEvents("startEventId");
213
+ const eventIterator = eventGenerator[Symbol.asyncIterator]();
214
+ await expect(eventIterator.next()).rejects.toThrow("Network error");
215
+ });
216
+
217
+ it("should not yield events when events array is empty and eventId unchanged", async () => {
218
+ const mockEventsResponse: DriveEventsListWithStatus = {
219
+ latestEventId: "sameEventId",
220
+ more: false,
221
+ refresh: false,
222
+ events: [],
223
+ };
224
+
225
+ mockEventsAPIService.getVolumeEvents.mockResolvedValue(mockEventsResponse);
226
+
227
+ const events = [];
228
+ for await (const event of manager.getEvents("sameEventId")) {
229
+ events.push(event);
230
+ }
231
+
232
+ expect(events).toHaveLength(0);
233
+ });
234
+ });
235
+
236
+ describe("getLogger", () => {
237
+ it("should return logger with prefix", () => {
238
+ const logger = manager.getLogger();
239
+ expect(logger).toBeDefined();
240
+ // The logger should be wrapped with LoggerWithPrefix, but we can't easily test the prefix
241
+ });
242
+ });
243
+ });
@@ -1,74 +1,76 @@
1
1
  import { Logger } from "../../interface";
2
2
  import { LoggerWithPrefix } from "../../telemetry";
3
3
  import { EventsAPIService } from "./apiService";
4
- import { EventsCache } from "./cache";
5
- import { DriveEvent, DriveListener } from "./interface";
6
- import { EventManager } from "./eventManager";
4
+ import { DriveEvent, DriveEventsListWithStatus, DriveEventType, EventManagerInterface, UnsubscribeFromEventsSourceError } from "./interface";
5
+ import { NotFoundAPIError } from "../apiService";
7
6
 
8
7
  /**
9
8
  * Combines API and event manager to provide a service for listening to
10
9
  * volume events. Volume events are all about nodes updates. Whenever
11
10
  * there is update to the node metadata or content, the event is emitted.
12
11
  */
13
- export class VolumeEventManager {
14
- private manager: EventManager<DriveEvent>;
12
+ export class VolumeEventManager implements EventManagerInterface<DriveEvent>{
15
13
 
16
- constructor(logger: Logger, private apiService: EventsAPIService, private cache: EventsCache, private volumeId: string, isOwnVolume: boolean) {
14
+ constructor(private logger: Logger, private apiService: EventsAPIService, private volumeId: string) {
17
15
  this.apiService = apiService;
18
16
  this.volumeId = volumeId;
19
-
20
- this.manager = new EventManager(
21
- new LoggerWithPrefix(logger, `volume ${volumeId}`),
22
- () => this.getLastEventId(),
23
- (eventId) => this.apiService.getVolumeEvents(volumeId, eventId, isOwnVolume),
24
- (lastEventId) => this.cache.setLastEventId(volumeId, {
25
- lastEventId,
26
- pollingIntervalInSeconds: this.manager.pollingIntervalInSeconds,
27
- isOwnVolume
28
- }),
29
- );
30
- this.cache.getPollingIntervalInSeconds(volumeId)
31
- .then((pollingIntervalInSeconds) => {
32
- if (pollingIntervalInSeconds) {
33
- this.manager.pollingIntervalInSeconds = pollingIntervalInSeconds;
34
- }
35
- })
36
- .catch(() => {});
37
- }
38
-
39
- private async getLastEventId(): Promise<string> {
40
- const lastEventId = await this.cache.getLastEventId(this.volumeId);
41
- if (lastEventId) {
42
- return lastEventId;
43
- }
44
- return this.apiService.getVolumeLatestEventId(this.volumeId);
45
- }
46
-
47
- /**
48
- * There is a limit how many volume subscribtions can be active at
49
- * the same time. The manager of all volume managers should set the
50
- * intervals for each volume accordingly depending on the volume
51
- * type or the total number of subscriptions.
52
- */
53
- setPollingInterval(pollingIntervalInSeconds: number): void {
54
- this.manager.pollingIntervalInSeconds = pollingIntervalInSeconds;
17
+ this.logger = new LoggerWithPrefix(logger, `volume ${volumeId}`);
55
18
  }
56
19
 
57
- async startSubscription(): Promise<void> {
58
- await this.manager.start();
20
+ getLogger(): Logger {
21
+ return this.logger;
59
22
  }
60
23
 
61
- async stopSubscription(): Promise<void> {
62
- await this.manager.stop();
24
+ async * getEvents(eventId: string): AsyncIterable<DriveEvent> {
25
+ try {
26
+ let events: DriveEventsListWithStatus;
27
+ let more = true;
28
+ while (more) {
29
+ events = await this.apiService.getVolumeEvents(this.volumeId, eventId);
30
+ more = events.more;
31
+ if (events.refresh) {
32
+ yield {
33
+ type: DriveEventType.TreeRefresh,
34
+ treeEventScopeId: this.volumeId,
35
+ eventId: events.latestEventId,
36
+ };
37
+ break;
38
+ }
39
+ // Update to the latest eventId to avoid inactive volumes from getting out of sync
40
+ if (events.events.length === 0 && events.latestEventId !== eventId) {
41
+ yield {
42
+ type: DriveEventType.FastForward,
43
+ treeEventScopeId: this.volumeId,
44
+ eventId: events.latestEventId,
45
+ };
46
+ break;
47
+ }
48
+ yield* events.events;
49
+ eventId = events.latestEventId;
50
+ }
51
+ } catch (error: unknown) {
52
+ if (error instanceof NotFoundAPIError) {
53
+ this.logger.info(`Volume events no longer accessible`);
54
+ yield {
55
+ type: DriveEventType.TreeRemove,
56
+ treeEventScopeId: this.volumeId,
57
+ // After a TreeRemoval event, polling should stop.
58
+ eventId: 'none',
59
+ };
60
+ }
61
+ throw error;
62
+ }
63
63
  }
64
64
 
65
- addListener(callback: DriveListener): void {
66
- this.manager.addListener(async (events, fullRefresh) => {
67
- if (fullRefresh) {
68
- await callback([], this.volumeId);
69
- } else {
70
- await callback(events);
65
+ async getLatestEventId(): Promise<string> {
66
+ try {
67
+ return await this.apiService.getVolumeLatestEventId(this.volumeId);
68
+ } catch (error: unknown) {
69
+ if (error instanceof NotFoundAPIError) {
70
+ this.logger.info(`Volume events no longer accessible`);
71
+ throw new UnsubscribeFromEventsSourceError(error.message);
71
72
  }
72
- });
73
+ throw error;
74
+ }
73
75
  }
74
76
  }
@@ -6,6 +6,7 @@ import { DecryptedNode, DecryptedRevision } from "./interface";
6
6
  export enum CACHE_TAG_KEYS {
7
7
  ParentUid = 'nodeParentUid',
8
8
  Trashed = 'nodeTrashed',
9
+ Roots = 'nodeRoot',
9
10
  }
10
11
 
11
12
  type DecryptedNodeResult = (
@@ -15,10 +16,10 @@ type DecryptedNodeResult = (
15
16
 
16
17
  /**
17
18
  * Provides caching for nodes metadata.
18
- *
19
+ *
19
20
  * The cache is responsible for serialising and deserialising node metadata,
20
21
  * recording parent-child relationships, and recursively removing nodes.
21
- *
22
+ *
22
23
  * The cache of node metadata should not contain any crypto material.
23
24
  */
24
25
  export class NodesCache {
@@ -35,6 +36,8 @@ export class NodesCache {
35
36
  const tags = [`volume:${volumeId}`];
36
37
  if (node.parentUid) {
37
38
  tags.push(`${CACHE_TAG_KEYS.ParentUid}:${node.parentUid}`)
39
+ } else {
40
+ tags.push(`${CACHE_TAG_KEYS.Roots}:${volumeId}`)
38
41
  }
39
42
  if (node.trashTime) {
40
43
  tags.push(`${CACHE_TAG_KEYS.Trashed}`)
@@ -74,6 +77,17 @@ export class NodesCache {
74
77
  }
75
78
  }
76
79
 
80
+ /**
81
+ * Remove all entries associated with a volume.
82
+ *
83
+ * This is needed when a user looses access to a volume.
84
+ */
85
+ async removeVolume(volumeId: string): Promise<void> {
86
+ for await (const result of this.iterateRootNodeUids(volumeId)) {
87
+ await this.removeNodes([result.key]);
88
+ }
89
+ }
90
+
77
91
  /**
78
92
  * Remove corrupted node never throws, but it logs so we can know
79
93
  * about issues and fix them. It is crucial to remove corrupted
@@ -142,6 +156,10 @@ export class NodesCache {
142
156
  }
143
157
  }
144
158
 
159
+ async *iterateRootNodeUids(volumeId: string): AsyncGenerator<EntityResult<string>> {
160
+ yield* this.driveCache.iterateEntitiesByTag(`${CACHE_TAG_KEYS.Roots}:${volumeId}`);
161
+ }
162
+
145
163
  async *iterateTrashedNodes(): AsyncGenerator<DecryptedNodeResult> {
146
164
  for await (const result of this.driveCache.iterateEntitiesByTag(CACHE_TAG_KEYS.Trashed)) {
147
165
  const node = await this.convertCacheResult(result);
@@ -422,7 +422,7 @@ export class NodesCryptoService {
422
422
  };
423
423
 
424
424
  async moveNode(
425
- node: DecryptedNode,
425
+ node: Pick<DecryptedNode, 'name'>,
426
426
  keys: { passphrase: string, passphraseSessionKey: SessionKey, nameSessionKey: SessionKey },
427
427
  parentKeys: { key: PrivateKey, hashKey: Uint8Array },
428
428
  address: { email: string, addressKey: PrivateKey },