@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
@@ -1,168 +1,122 @@
1
1
  import { Logger } from "../../interface";
2
- import { NotFoundAPIError } from "../apiService";
3
- import { Events } from "./interface";
2
+ import { EventManagerInterface, Event, EventSubscription } from "./interface";
4
3
 
5
- const DEFAULT_POLLING_INTERVAL_IN_SECONDS = 30;
6
4
  const FIBONACCI_LIST = [1, 1, 2, 3, 5, 8, 13];
7
5
 
8
- /**
9
- * `fullRefresh` is true when the event manager has requested a full
10
- * refresh of the data. That can happen if there is too many events
11
- * to be processed or the last event ID is too old.
12
- */
13
- type Listener<T> = (events: T[], fullRefresh: boolean) => Promise<void>;
6
+ type Listener<T> = (event: T) => Promise<void>;
7
+
14
8
 
15
9
  /**
16
10
  * Event manager general helper that is responsible for fetching events
17
11
  * from the server and notifying listeners about the events.
18
- *
12
+ *
19
13
  * The specific implementation of fetching the events from the API must
20
14
  * be passed as dependency and can be used for any type of events that
21
15
  * supports the same structure.
22
- *
16
+ *
23
17
  * The manager will not start fetching events until the `start` method is
24
18
  * called. Once started, the manager will fetch events in a loop with
25
19
  * a timeout between each fetch. The default timeout is 30 seconds and
26
20
  * additional jitter is used in case of failure.
27
- *
28
- * Example of usage:
29
- *
30
- * ```typescript
31
- * const manager = new EventManager(
32
- * logger,
33
- * () => apiService.getLatestEventId(),
34
- * (eventId) => apiService.getEvents(eventId),
35
- * );
36
- *
37
- * manager.addListener((events, fullRefresh) => {
38
- * // Process the events
39
- * });
40
- *
41
- * manager.start();
42
- * ```
43
21
  */
44
- export class EventManager<T> {
22
+ export class EventManager<T extends Event> {
23
+ private logger: Logger;
45
24
  private latestEventId?: string;
46
25
  private timeoutHandle?: ReturnType<typeof setTimeout>;
47
26
  private processPromise?: Promise<void>;
48
27
  private listeners: Listener<T>[] = [];
49
28
  private retryIndex: number = 0;
50
29
 
51
- pollingIntervalInSeconds = DEFAULT_POLLING_INTERVAL_IN_SECONDS;
52
-
53
30
  constructor(
54
- private logger: Logger,
55
- private getLatestEventId: () => Promise<string>,
56
- private getEvents: (eventId: string) => Promise<Events<T>>,
57
- private updateLatestEventId: (lastEventId: string) => Promise<void>,
31
+ private specializedEventManager: EventManagerInterface<T>,
32
+ private pollingIntervalInSeconds: number,
33
+ latestEventId: string | null,
58
34
  ) {
59
- this.logger = logger;
60
- this.getLatestEventId = getLatestEventId;
61
- this.getEvents = getEvents;
62
- this.updateLatestEventId = updateLatestEventId;
35
+ if (latestEventId !== null) {
36
+ this.latestEventId = latestEventId;
37
+ }
38
+ this.logger = specializedEventManager.getLogger();
63
39
  }
64
40
 
65
- addListener(callback: Listener<T>): void {
41
+ async start(): Promise<void> {
42
+ if (this.latestEventId === undefined) {
43
+ this.latestEventId = await this.specializedEventManager.getLatestEventId();
44
+ }
45
+ this.processPromise = this.processEvents();
46
+ }
47
+
48
+ addListener(callback: Listener<T>): EventSubscription {
66
49
  this.listeners.push(callback);
50
+ return {
51
+ dispose: (): void => {
52
+ const index = this.listeners.indexOf(callback);
53
+ this.listeners.splice(index, 1);
54
+ },
55
+ };
67
56
  }
68
57
 
69
- async start(): Promise<void> {
70
- this.logger.info(`Starting event manager with polling interval ${this.pollingIntervalInSeconds} seconds`);
71
- await this.stop();
72
- this.processPromise = this.processEvents();
58
+ setPollingInterval(pollingIntervalInSeconds: number): void {
59
+ this.pollingIntervalInSeconds = pollingIntervalInSeconds;
60
+ }
61
+
62
+ async stop(): Promise<void> {
63
+ if (this.processPromise) {
64
+ this.logger.info(`Stopping event manager`);
65
+ try {
66
+ await this.processPromise;
67
+ } catch (error) {
68
+ this.logger.warn(`Failed to stop cleanly: ${error instanceof Error ? error.message : error}`);
69
+ }
70
+ }
71
+
72
+ if (!this.timeoutHandle) {
73
+ return;
74
+ }
75
+
76
+ clearTimeout(this.timeoutHandle);
77
+ this.timeoutHandle = undefined;
78
+ }
79
+
80
+ private async notifyListeners(event: T): Promise<void> {
81
+ for (const listener of this.listeners) {
82
+ await listener(event);
83
+ }
73
84
  }
74
85
 
75
86
  private async processEvents() {
87
+ let listenerError;
76
88
  try {
77
- if (!this.latestEventId) {
78
- this.latestEventId = await this.getLatestEventId();
79
- await this.updateLatestEventId(this.latestEventId);
80
- } else {
81
- while (true) {
82
- let result;
83
- try {
84
- result = await this.getEvents(this.latestEventId);
85
- } catch (error: unknown) {
86
- // If last event ID is not found, we need to refresh the data.
87
- // Caller is notified via standard event update with refresh flag.
88
- if (error instanceof NotFoundAPIError) {
89
- this.logger.warn(`Last event ID not found, refreshing data`);
90
- result = {
91
- lastEventId: await this.getLatestEventId(),
92
- more: false,
93
- refresh: true,
94
- events: [],
95
- };
96
- } else {
97
- // Any other error is considered as a failure and we will retry
98
- // with backoff policy.
99
- throw error;
100
- }
101
- }
102
- await this.notifyListeners(result);
103
- if (result.lastEventId !== this.latestEventId) {
104
- await this.updateLatestEventId(result.lastEventId);
105
- this.latestEventId = result.lastEventId;
106
- }
107
- if (!result.more) {
108
- break;
109
- }
89
+ const events = this.specializedEventManager.getEvents(this.latestEventId!);
90
+ for await (const event of events) {
91
+ try {
92
+ await this.notifyListeners(event);
93
+ } catch (internalListenerError) {
94
+ listenerError = internalListenerError;
95
+ break;
110
96
  }
97
+ this.latestEventId = event.eventId;
111
98
  }
112
99
  this.retryIndex = 0;
113
100
  } catch (error: unknown) {
101
+ // This could be improved to catch api specific errors and let the listener errors bubble up directly
114
102
  this.logger.error(`Failed to process events: ${error instanceof Error ? error.message : error} (retry ${this.retryIndex}, last event ID: ${this.latestEventId})`);
115
103
  this.retryIndex++;
116
104
  }
105
+ if (listenerError) {
106
+ throw listenerError;
107
+ }
117
108
 
118
109
  this.timeoutHandle = setTimeout(() => {
119
110
  this.processPromise = this.processEvents();
120
111
  }, this.nextPollTimeout);
121
112
  };
122
113
 
123
- private async notifyListeners(result: Events<T>): Promise<void> {
124
- if (result.events.length === 0 && !result.refresh) {
125
- return;
126
- }
127
- if (!this.listeners.length) {
128
- return;
129
- }
130
-
131
- this.logger.debug(`Notifying listeners about ${result.events.length} events`);
132
-
133
- for (const listener of this.listeners) {
134
- try {
135
- await listener(result.events, result.refresh);
136
- } catch (error: unknown) {
137
- this.logger.error(`Failed to process events: ${error instanceof Error ? error.message : error} (last event ID: ${result.lastEventId}, refresh: ${result.refresh})`);
138
- throw error;
139
- }
140
- }
141
- }
142
-
143
114
  /**
144
115
  * Polling timeout is using exponential backoff with Fibonacci sequence.
145
- *
146
- * The timeout is public for testing purposes only.
147
116
  */
148
- get nextPollTimeout(): number {
117
+ private get nextPollTimeout(): number {
149
118
  const retryIndex = Math.min(this.retryIndex, FIBONACCI_LIST.length - 1);
119
+ // FIXME jitter
150
120
  return this.pollingIntervalInSeconds * 1000 * FIBONACCI_LIST[retryIndex];
151
121
  }
152
-
153
- async stop(): Promise<void> {
154
- if (this.processPromise) {
155
- this.logger.info(`Stopping event manager`);
156
- try {
157
- await this.processPromise;
158
- } catch {}
159
- }
160
-
161
- if (!this.timeoutHandle) {
162
- return;
163
- }
164
-
165
- clearTimeout(this.timeoutHandle);
166
- this.timeoutHandle = undefined;
167
- }
168
122
  }
@@ -1,16 +1,18 @@
1
- import { ProtonDriveEntitiesCache, Logger, ProtonDriveTelemetry } from "../../interface";
1
+ import { Logger, ProtonDriveTelemetry } from "../../interface";
2
2
  import { DriveAPIService } from "../apiService";
3
- import { DriveListener } from "./interface";
3
+ import { DriveEvent, DriveListener, EventSubscription, LatestEventIdProvider } from "./interface";
4
4
  import { EventsAPIService } from "./apiService";
5
- import { EventsCache } from "./cache";
6
5
  import { CoreEventManager } from "./coreEventManager";
7
6
  import { VolumeEventManager } from "./volumeEventManager";
7
+ import { EventManager } from "./eventManager";
8
+ import { SharesManager } from "../shares/manager";
8
9
 
9
10
  export type { DriveEvent, DriveListener } from "./interface";
10
11
  export { DriveEventType } from "./interface";
11
12
 
12
13
  const OWN_VOLUME_POLLING_INTERVAL = 30;
13
14
  const OTHER_VOLUME_POLLING_INTERVAL = 60;
15
+ const CORE_POLLING_INTERVAL = 30;
14
16
 
15
17
  /**
16
18
  * Service for listening to drive events. The service is responsible for
@@ -19,113 +21,81 @@ const OTHER_VOLUME_POLLING_INTERVAL = 60;
19
21
  */
20
22
  export class DriveEventsService {
21
23
  private apiService: EventsAPIService;
22
- private cache: EventsCache;
23
- private subscribedToRemoteDataUpdates: boolean = false;
24
- private listeners: DriveListener[] = [];
25
- private coreEvents: CoreEventManager;
26
- private volumesEvents: { [volumeId: string]: VolumeEventManager };
24
+ private coreEvents?: EventManager<DriveEvent>;
25
+ private volumeEventManagers: { [volumeId: string]: EventManager<DriveEvent> };
27
26
  private logger: Logger;
28
27
 
29
- constructor(private telemetry: ProtonDriveTelemetry, apiService: DriveAPIService, driveEntitiesCache: ProtonDriveEntitiesCache) {
28
+ constructor(private telemetry: ProtonDriveTelemetry, apiService: DriveAPIService, private shareManagement: SharesManager, private cacheEventListeners: DriveListener[] = [], private latestEventIdProvider?: LatestEventIdProvider) {
30
29
  this.telemetry = telemetry;
31
30
  this.logger = telemetry.getLogger('events');
32
31
  this.apiService = new EventsAPIService(apiService);
33
- this.cache = new EventsCache(driveEntitiesCache);
34
-
35
- // FIXME: Allow to pass own core events manager from the public interface.
36
- this.coreEvents = new CoreEventManager(this.logger, this.apiService, this.cache);
37
- this.volumesEvents = {};
32
+ this.volumeEventManagers = {};
38
33
  }
39
34
 
40
35
  /**
41
- * Loads all the subscribed volumes (including core events) from the
42
- * cache and starts listening to their events. Any additional volume
43
- * that is subscribed to later will be automatically started.
36
+ * Subscribe to drive events. The treeEventScopeId can be obtained from a node.
44
37
  */
45
- async subscribeToRemoteDataUpdates(): Promise<void> {
46
- if (this.subscribedToRemoteDataUpdates) {
47
- return;
48
- }
49
-
50
- await this.loadSubscribedVolumeEventServices();
51
- this.sendNumberOfVolumeSubscriptionsToTelemetry();
52
-
53
- this.subscribedToRemoteDataUpdates = true;
54
- await this.coreEvents.startSubscription();
55
- await Promise.all(
56
- Object.values(this.volumesEvents)
57
- .map((volumeEvents) => volumeEvents.startSubscription())
58
- );
59
- }
60
-
61
- /**
62
- * Subscribe to given volume. The volume will be polled for events
63
- * with the polling interval depending on the type of the volume.
64
- * Own volumes are polled with highest frequency, while others are
65
- * polled with lower frequency depending on the total number of
66
- * subscriptions.
67
- *
68
- * @param isOwnVolume - Owned volumes are polled with higher frequency.
69
- */
70
- async listenToVolume(volumeId: string, isOwnVolume = false): Promise<void> {
71
- await this.loadSubscribedVolumeEventServices();
72
-
73
- if (this.volumesEvents[volumeId]) {
74
- return;
75
- }
38
+ async subscribeToTreeEvents(treeEventScopeId: string, callback: DriveListener): Promise<EventSubscription> {
39
+ const volumeId = treeEventScopeId;
76
40
  this.logger.debug(`Creating volume event manager for volume ${volumeId}`);
77
- const manager = this.createVolumeEventManager(volumeId, isOwnVolume);
78
-
79
- // FIXME: Use dynamic algorithm to determine polling interval for non-own volumes.
80
- manager.setPollingInterval(isOwnVolume ? OWN_VOLUME_POLLING_INTERVAL : OTHER_VOLUME_POLLING_INTERVAL);
81
- if (this.subscribedToRemoteDataUpdates) {
82
- await manager.startSubscription();
41
+ let manager = this.volumeEventManagers[volumeId];
42
+ let started = true;
43
+ if (manager === undefined) {
44
+ manager = await this.createVolumeEventManager(volumeId);
45
+ this.volumeEventManagers[volumeId] = manager;
46
+ started = false;
83
47
  this.sendNumberOfVolumeSubscriptionsToTelemetry();
84
48
  }
49
+ const eventSubscription = manager.addListener(callback);
50
+ if (!started) {
51
+ await manager.start();
52
+ }
53
+ return eventSubscription;
85
54
  }
86
55
 
87
- private async loadSubscribedVolumeEventServices() {
88
- for (const volumeId of await this.cache.getSubscribedVolumeIds()) {
89
- if (!this.volumesEvents[volumeId]) {
90
- const isOwnVolume = await this.cache.isOwnVolume(volumeId) || false;
91
- this.createVolumeEventManager(volumeId, isOwnVolume);
56
+ // FIXME: Allow to pass own core events manager from the public interface.
57
+ async subscribeToCoreEvents(callback: DriveListener): Promise<EventSubscription> {
58
+ if (this.latestEventIdProvider === null || this.latestEventIdProvider === undefined) {
59
+ throw new Error('Cannot subscribe to events without passing a latestEventIdProvider in ProtonDriveClient initialization');
60
+ }
61
+ if (this.coreEvents === undefined) {
62
+ const coreEventManager = new CoreEventManager(this.logger, this.apiService);
63
+ const latestEventId = this.latestEventIdProvider.getLatestEventId('core') ?? null;
64
+ this.coreEvents = new EventManager(coreEventManager, CORE_POLLING_INTERVAL, latestEventId);
65
+ for (const listener of this.cacheEventListeners) {
66
+ this.coreEvents.addListener(listener);
92
67
  }
93
68
  }
69
+ const eventSubscription = this.coreEvents.addListener(callback);
70
+ await this.coreEvents.start();
71
+ return eventSubscription;
94
72
  }
95
73
 
96
74
  private sendNumberOfVolumeSubscriptionsToTelemetry() {
97
75
  this.telemetry.logEvent({
98
76
  eventName: 'volumeEventsSubscriptionsChanged',
99
- numberOfVolumeSubscriptions: Object.keys(this.volumesEvents).length,
77
+ numberOfVolumeSubscriptions: Object.keys(this.volumeEventManagers).length,
100
78
  });
101
79
  }
102
80
 
103
- private createVolumeEventManager(volumeId: string, isOwnVolume: boolean): VolumeEventManager {
104
- const manager = new VolumeEventManager(this.logger, this.apiService, this.cache, volumeId, isOwnVolume);
105
- for (const listener of this.listeners) {
106
- manager.addListener(listener);
81
+ private async createVolumeEventManager(volumeId: string): Promise<EventManager<DriveEvent>> {
82
+ if (this.latestEventIdProvider === null || this.latestEventIdProvider === undefined) {
83
+ throw new Error('Cannot subscribe to events without passing a latestEventIdProvider in ProtonDriveClient initialization');
84
+ }
85
+ const isOwnVolume = await this.shareManagement.isOwnVolume(volumeId);
86
+ const pollingInterval = this.getDefaultVolumePollingInterval(isOwnVolume);
87
+ const volumeEventManager = new VolumeEventManager(this.logger, this.apiService, volumeId);
88
+ const latestEventId = this.latestEventIdProvider.getLatestEventId(volumeId);
89
+ const eventManager = new EventManager<DriveEvent>(volumeEventManager, pollingInterval, latestEventId);
90
+ for (const listener of this.cacheEventListeners) {
91
+ eventManager.addListener(listener);
107
92
  }
108
- this.volumesEvents[volumeId] = manager;
109
- return manager;
93
+ await eventManager.start();
94
+ this.volumeEventManagers[volumeId] = eventManager;
95
+ return eventManager;
110
96
  }
111
97
 
112
- /**
113
- * Listen to the drive events. The listener will be called with the
114
- * new events as they arrive.
115
- *
116
- * One call always provides events from withing the same volume. The
117
- * second argument of the callback `fullRefreshVolumeId` is thus single
118
- * ID and if multiple volumes must be fully refreshed, client will
119
- * receive multiple calls.
120
- */
121
- addListener(callback: DriveListener): void {
122
- // Add new listener to the list for any new event manager.
123
- this.listeners.push(callback);
124
-
125
- // Add new listener to all existings managers.
126
- this.coreEvents.addListener(callback);
127
- for (const volumeEvents of Object.values(this.volumesEvents)) {
128
- volumeEvents.addListener(callback);
129
- }
98
+ private getDefaultVolumePollingInterval(isOwnVolume: boolean): number {
99
+ return isOwnVolume ? OWN_VOLUME_POLLING_INTERVAL : OTHER_VOLUME_POLLING_INTERVAL
130
100
  }
131
101
  }
@@ -1,19 +1,33 @@
1
+ import { Logger } from "../../interface";
2
+
1
3
  /**
2
4
  * Callback that accepts list of Drive events and flag whether no
3
5
  * event should be processed, but rather full cache refresh should be
4
6
  * performed.
5
- *
7
+ *
6
8
  * @param fullRefreshVolumeId - ID of the volume that should be fully refreshed.
7
9
  */
8
- export type DriveListener = (events: DriveEvent[], fullRefreshVolumeId?: string) => Promise<void>;
10
+ export type DriveListener = (event: DriveEvent) => Promise<void>;
11
+
12
+ export interface Event {
13
+ eventId: string;
14
+ }
15
+
16
+ export interface EventSubscription {
17
+ dispose(): void;
18
+ }
19
+
20
+ export interface LatestEventIdProvider {
21
+ getLatestEventId(treeEventScopeId: string): string | null;
22
+ }
9
23
 
10
24
  /**
11
25
  * Generic internal event interface representing a list of events
12
26
  * with metadata about the last event ID, whether there are more
13
27
  * events to fetch, or whether the listener should refresh its state.
14
28
  */
15
- export type Events<T> = {
16
- lastEventId: string,
29
+ export type EventsListWithStatus<T> = {
30
+ latestEventId: string,
17
31
  more: boolean,
18
32
  refresh: boolean,
19
33
  events: T[],
@@ -22,30 +36,71 @@ export type Events<T> = {
22
36
  /**
23
37
  * Internal event interface representing a list of specific Drive events.
24
38
  */
25
- export type DriveEvents = Events<DriveEvent>;
39
+ export type DriveEventsListWithStatus = EventsListWithStatus<DriveEvent>;
40
+
41
+ type NodeCruEventType = DriveEventType.NodeCreated | DriveEventType.NodeUpdated;
42
+ export type NodeEventType = NodeCruEventType | DriveEventType.NodeDeleted;
26
43
 
27
- export type DriveEvent = {
28
- type: DriveEventType.NodeCreated | DriveEventType.NodeUpdated | DriveEventType.NodeUpdatedMetadata,
44
+ export type NodeEvent = {
45
+ type: NodeCruEventType,
29
46
  nodeUid: string,
30
47
  parentNodeUid?: string,
31
48
  isTrashed: boolean,
32
49
  isShared: boolean,
33
- isOwnVolume: boolean,
50
+ treeEventScopeId: string,
51
+ eventId: string,
34
52
  } | {
35
53
  type: DriveEventType.NodeDeleted,
36
54
  nodeUid: string,
37
55
  parentNodeUid?: string,
38
- isTrashed?: boolean,
39
- isShared?: boolean,
40
- isOwnVolume: boolean,
41
- } | {
42
- type: DriveEventType.ShareWithMeUpdated,
56
+ treeEventScopeId: string,
57
+ eventId: string,
43
58
  }
44
59
 
60
+ export type FastForwardEvent = {
61
+ type: DriveEventType.FastForward,
62
+ treeEventScopeId: string,
63
+ eventId: string,
64
+ }
65
+
66
+ export type TreeRefreshEvent = {
67
+ type: DriveEventType.TreeRefresh,
68
+ treeEventScopeId: string,
69
+ eventId: string,
70
+ }
71
+
72
+ export type TreeRemovalEvent = {
73
+ type: DriveEventType.TreeRemove,
74
+ treeEventScopeId: string,
75
+ eventId: 'none',
76
+ }
77
+
78
+ export type SharedWithMeUpdated = {
79
+ type: DriveEventType.SharedWithMeUpdated,
80
+ eventId: string,
81
+ treeEventScopeId: 'core',
82
+ }
83
+
84
+ export type DriveEvent = NodeEvent | FastForwardEvent | TreeRefreshEvent | TreeRemovalEvent | FastForwardEvent | SharedWithMeUpdated;
85
+
45
86
  export enum DriveEventType {
46
87
  NodeCreated = 'node_created',
47
88
  NodeUpdated = 'node_updated',
48
- NodeUpdatedMetadata = 'node_updated_metadata',
49
89
  NodeDeleted = 'node_deleted',
50
- ShareWithMeUpdated = 'share_with_me_updated',
90
+ SharedWithMeUpdated = 'shared_with_me_updated',
91
+ TreeRefresh = 'tree_refresh',
92
+ TreeRemove = 'tree_remove',
93
+ FastForward = 'fast_forward',
94
+ }
95
+
96
+ /**
97
+ * This can happen if all shared nodes in that volume where unshared or if the
98
+ * volume was deleted.
99
+ */
100
+ export class UnsubscribeFromEventsSourceError extends Error {};
101
+
102
+ export interface EventManagerInterface<T> {
103
+ getLatestEventId(): Promise<string>;
104
+ getEvents(eventId: string): AsyncIterable<T>;
105
+ getLogger(): Logger;
51
106
  }