@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.
- package/dist/cache/index.d.ts +1 -0
- package/dist/cache/index.js +3 -1
- package/dist/cache/index.js.map +1 -1
- package/dist/cache/memoryCache.d.ts +1 -1
- package/dist/cache/nullCache.d.ts +14 -0
- package/dist/cache/nullCache.js +37 -0
- package/dist/cache/nullCache.js.map +1 -0
- package/dist/config.d.ts +16 -1
- package/dist/config.js +1 -1
- package/dist/config.js.map +1 -1
- package/dist/crypto/openPGPCrypto.js +2 -0
- package/dist/crypto/openPGPCrypto.js.map +1 -1
- package/dist/diagnostic/eventsGenerator.d.ts +14 -0
- package/dist/diagnostic/eventsGenerator.js +49 -0
- package/dist/diagnostic/eventsGenerator.js.map +1 -0
- package/dist/diagnostic/httpClient.d.ts +16 -0
- package/dist/diagnostic/httpClient.js +81 -0
- package/dist/diagnostic/httpClient.js.map +1 -0
- package/dist/diagnostic/index.d.ts +10 -0
- package/dist/diagnostic/index.js +35 -0
- package/dist/diagnostic/index.js.map +1 -0
- package/dist/diagnostic/integrityVerificationStream.d.ts +21 -0
- package/dist/diagnostic/integrityVerificationStream.js +56 -0
- package/dist/diagnostic/integrityVerificationStream.js.map +1 -0
- package/dist/diagnostic/interface.d.ts +102 -0
- package/dist/diagnostic/interface.js +3 -0
- package/dist/diagnostic/interface.js.map +1 -0
- package/dist/diagnostic/sdkDiagnostic.d.ts +22 -0
- package/dist/diagnostic/sdkDiagnostic.js +216 -0
- package/dist/diagnostic/sdkDiagnostic.js.map +1 -0
- package/dist/diagnostic/sdkDiagnosticFull.d.ts +18 -0
- package/dist/diagnostic/sdkDiagnosticFull.js +35 -0
- package/dist/diagnostic/sdkDiagnosticFull.js.map +1 -0
- package/dist/diagnostic/telemetry.d.ts +25 -0
- package/dist/diagnostic/telemetry.js +70 -0
- package/dist/diagnostic/telemetry.js.map +1 -0
- package/dist/diagnostic/zipGenerators.d.ts +9 -0
- package/dist/diagnostic/zipGenerators.js +64 -0
- package/dist/diagnostic/zipGenerators.js.map +1 -0
- package/dist/diagnostic/zipGenerators.test.js +144 -0
- package/dist/diagnostic/zipGenerators.test.js.map +1 -0
- package/dist/errors.d.ts +2 -1
- package/dist/errors.js +3 -1
- package/dist/errors.js.map +1 -1
- package/dist/interface/config.d.ts +26 -0
- package/dist/interface/config.js +3 -0
- package/dist/interface/config.js.map +1 -0
- package/dist/interface/download.d.ts +2 -2
- package/dist/interface/events.d.ts +60 -20
- package/dist/interface/events.js +11 -1
- package/dist/interface/events.js.map +1 -1
- package/dist/interface/httpClient.d.ts +0 -14
- package/dist/interface/index.d.ts +8 -4
- package/dist/interface/index.js +2 -1
- package/dist/interface/index.js.map +1 -1
- package/dist/interface/nodes.d.ts +9 -0
- package/dist/interface/nodes.js.map +1 -1
- package/dist/interface/sharing.d.ts +1 -0
- package/dist/interface/upload.d.ts +6 -0
- package/dist/internal/download/apiService.js +32 -31
- package/dist/internal/download/apiService.js.map +1 -1
- package/dist/internal/download/fileDownloader.d.ts +2 -2
- package/dist/internal/download/fileDownloader.js.map +1 -1
- package/dist/internal/events/apiService.d.ts +4 -6
- package/dist/internal/events/apiService.js +15 -22
- package/dist/internal/events/apiService.js.map +1 -1
- package/dist/internal/events/coreEventManager.d.ts +7 -10
- package/dist/internal/events/coreEventManager.js +19 -36
- package/dist/internal/events/coreEventManager.js.map +1 -1
- package/dist/internal/events/coreEventManager.test.js +87 -0
- package/dist/internal/events/coreEventManager.test.js.map +1 -0
- package/dist/internal/events/eventManager.d.ts +11 -36
- package/dist/internal/events/eventManager.js +59 -105
- package/dist/internal/events/eventManager.js.map +1 -1
- package/dist/internal/events/eventManager.test.js +167 -82
- package/dist/internal/events/eventManager.test.js.map +1 -1
- package/dist/internal/events/index.d.ts +13 -33
- package/dist/internal/events/index.js +56 -72
- package/dist/internal/events/index.js.map +1 -1
- package/dist/internal/events/interface.d.ts +59 -14
- package/dist/internal/events/interface.js +13 -3
- package/dist/internal/events/interface.js.map +1 -1
- package/dist/internal/events/volumeEventManager.d.ts +7 -17
- package/dist/internal/events/volumeEventManager.js +58 -45
- package/dist/internal/events/volumeEventManager.js.map +1 -1
- package/dist/internal/events/volumeEventManager.test.d.ts +1 -0
- package/dist/internal/events/volumeEventManager.test.js +203 -0
- package/dist/internal/events/volumeEventManager.test.js.map +1 -0
- package/dist/internal/nodes/cache.d.ts +10 -1
- package/dist/internal/nodes/cache.js +17 -0
- package/dist/internal/nodes/cache.js.map +1 -1
- package/dist/internal/nodes/cryptoService.d.ts +1 -1
- package/dist/internal/nodes/cryptoService.js.map +1 -1
- package/dist/internal/nodes/events.d.ts +7 -83
- package/dist/internal/nodes/events.js +43 -217
- package/dist/internal/nodes/events.js.map +1 -1
- package/dist/internal/nodes/events.test.js +27 -277
- package/dist/internal/nodes/events.test.js.map +1 -1
- package/dist/internal/nodes/index.d.ts +3 -4
- package/dist/internal/nodes/index.js +5 -5
- package/dist/internal/nodes/index.js.map +1 -1
- package/dist/internal/nodes/nodesAccess.d.ts +15 -0
- package/dist/internal/nodes/nodesAccess.js +37 -0
- package/dist/internal/nodes/nodesAccess.js.map +1 -1
- package/dist/internal/nodes/nodesAccess.test.js +131 -93
- package/dist/internal/nodes/nodesAccess.test.js.map +1 -1
- package/dist/internal/nodes/nodesManagement.d.ts +1 -3
- package/dist/internal/nodes/nodesManagement.js +12 -26
- package/dist/internal/nodes/nodesManagement.js.map +1 -1
- package/dist/internal/nodes/nodesManagement.test.js +35 -14
- package/dist/internal/nodes/nodesManagement.test.js.map +1 -1
- package/dist/internal/shares/cache.d.ts +2 -0
- package/dist/internal/shares/cache.js +2 -0
- package/dist/internal/shares/cache.js.map +1 -1
- package/dist/internal/shares/manager.d.ts +1 -0
- package/dist/internal/shares/manager.js +3 -0
- package/dist/internal/shares/manager.js.map +1 -1
- package/dist/internal/sharing/apiService.js +1 -0
- package/dist/internal/sharing/apiService.js.map +1 -1
- package/dist/internal/sharing/cryptoService.js +1 -0
- package/dist/internal/sharing/cryptoService.js.map +1 -1
- package/dist/internal/sharing/events.d.ts +23 -55
- package/dist/internal/sharing/events.js +46 -138
- package/dist/internal/sharing/events.js.map +1 -1
- package/dist/internal/sharing/events.test.js +77 -180
- package/dist/internal/sharing/events.test.js.map +1 -1
- package/dist/internal/sharing/index.d.ts +4 -5
- package/dist/internal/sharing/index.js +5 -5
- package/dist/internal/sharing/index.js.map +1 -1
- package/dist/internal/sharing/interface.d.ts +3 -0
- package/dist/internal/sharing/sharingManagement.d.ts +2 -3
- package/dist/internal/sharing/sharingManagement.js +7 -9
- package/dist/internal/sharing/sharingManagement.js.map +1 -1
- package/dist/internal/sharing/sharingManagement.test.js +9 -39
- package/dist/internal/sharing/sharingManagement.test.js.map +1 -1
- package/dist/internal/upload/apiService.d.ts +2 -3
- package/dist/internal/upload/apiService.js +7 -4
- package/dist/internal/upload/apiService.js.map +1 -1
- package/dist/internal/upload/index.d.ts +2 -2
- package/dist/internal/upload/index.js +3 -3
- package/dist/internal/upload/index.js.map +1 -1
- package/dist/internal/upload/interface.d.ts +2 -0
- package/dist/internal/upload/manager.d.ts +5 -5
- package/dist/internal/upload/manager.js +19 -50
- package/dist/internal/upload/manager.js.map +1 -1
- package/dist/internal/upload/manager.test.js +68 -44
- package/dist/internal/upload/manager.test.js.map +1 -1
- package/dist/internal/upload/streamUploader.js +1 -2
- package/dist/internal/upload/streamUploader.js.map +1 -1
- package/dist/internal/upload/streamUploader.test.js +1 -1
- package/dist/internal/upload/streamUploader.test.js.map +1 -1
- package/dist/protonDriveClient.d.ts +19 -162
- package/dist/protonDriveClient.js +26 -190
- package/dist/protonDriveClient.js.map +1 -1
- package/dist/protonDrivePhotosClient.js +3 -2
- package/dist/protonDrivePhotosClient.js.map +1 -1
- package/package.json +3 -3
- package/src/cache/index.ts +1 -0
- package/src/cache/memoryCache.ts +1 -1
- package/src/cache/nullCache.ts +38 -0
- package/src/config.ts +17 -2
- package/src/crypto/openPGPCrypto.ts +2 -0
- package/src/diagnostic/eventsGenerator.ts +48 -0
- package/src/diagnostic/httpClient.ts +80 -0
- package/src/diagnostic/index.ts +38 -0
- package/src/diagnostic/integrityVerificationStream.ts +56 -0
- package/src/diagnostic/interface.ts +158 -0
- package/src/diagnostic/sdkDiagnostic.ts +238 -0
- package/src/diagnostic/sdkDiagnosticFull.ts +40 -0
- package/src/diagnostic/telemetry.ts +71 -0
- package/src/diagnostic/zipGenerators.test.ts +177 -0
- package/src/diagnostic/zipGenerators.ts +70 -0
- package/src/errors.ts +4 -1
- package/src/interface/config.ts +28 -0
- package/src/interface/download.ts +2 -2
- package/src/interface/events.ts +66 -21
- package/src/interface/httpClient.ts +0 -16
- package/src/interface/index.ts +8 -4
- package/src/interface/nodes.ts +21 -12
- package/src/interface/sharing.ts +1 -0
- package/src/interface/upload.ts +6 -0
- package/src/internal/download/apiService.ts +11 -8
- package/src/internal/download/fileDownloader.ts +2 -2
- package/src/internal/events/apiService.ts +25 -28
- package/src/internal/events/coreEventManager.test.ts +101 -0
- package/src/internal/events/coreEventManager.ts +20 -45
- package/src/internal/events/eventManager.test.ts +201 -88
- package/src/internal/events/eventManager.ts +69 -115
- package/src/internal/events/index.ts +54 -84
- package/src/internal/events/interface.ts +70 -15
- package/src/internal/events/volumeEventManager.test.ts +243 -0
- package/src/internal/events/volumeEventManager.ts +55 -53
- package/src/internal/nodes/cache.ts +20 -2
- package/src/internal/nodes/cryptoService.ts +1 -1
- package/src/internal/nodes/events.test.ts +29 -335
- package/src/internal/nodes/events.ts +45 -253
- package/src/internal/nodes/index.ts +6 -8
- package/src/internal/nodes/interface.ts +2 -2
- package/src/internal/nodes/nodesAccess.test.ts +132 -91
- package/src/internal/nodes/nodesAccess.ts +40 -1
- package/src/internal/nodes/nodesManagement.test.ts +39 -15
- package/src/internal/nodes/nodesManagement.ts +12 -30
- package/src/internal/shares/cache.ts +4 -2
- package/src/internal/shares/manager.ts +9 -5
- package/src/internal/sharing/apiService.ts +1 -0
- package/src/internal/sharing/cache.ts +1 -1
- package/src/internal/sharing/cryptoService.ts +1 -0
- package/src/internal/sharing/events.test.ts +89 -195
- package/src/internal/sharing/events.ts +42 -156
- package/src/internal/sharing/index.ts +6 -9
- package/src/internal/sharing/interface.ts +6 -2
- package/src/internal/sharing/sharingManagement.test.ts +10 -40
- package/src/internal/sharing/sharingManagement.ts +7 -11
- package/src/internal/upload/apiService.ts +5 -6
- package/src/internal/upload/index.ts +5 -5
- package/src/internal/upload/interface.ts +2 -0
- package/src/internal/upload/manager.test.ts +75 -45
- package/src/internal/upload/manager.ts +24 -54
- package/src/internal/upload/streamUploader.test.ts +0 -1
- package/src/internal/upload/streamUploader.ts +0 -2
- package/src/protonDriveClient.ts +75 -244
- package/src/protonDrivePhotosClient.ts +4 -3
- package/dist/internal/events/cache.d.ts +0 -28
- package/dist/internal/events/cache.js +0 -67
- package/dist/internal/events/cache.js.map +0 -1
- package/dist/internal/events/cache.test.js +0 -43
- package/dist/internal/events/cache.test.js.map +0 -1
- package/dist/internal/nodes/index.test.js +0 -114
- package/dist/internal/nodes/index.test.js.map +0 -1
- package/src/internal/events/cache.test.ts +0 -47
- package/src/internal/events/cache.ts +0 -80
- package/src/internal/nodes/index.test.ts +0 -137
- /package/dist/{internal/events/cache.test.d.ts → diagnostic/zipGenerators.test.d.ts} +0 -0
- /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 {
|
|
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
|
-
|
|
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
|
|
55
|
-
private
|
|
56
|
-
|
|
57
|
-
private updateLatestEventId: (lastEventId: string) => Promise<void>,
|
|
31
|
+
private specializedEventManager: EventManagerInterface<T>,
|
|
32
|
+
private pollingIntervalInSeconds: number,
|
|
33
|
+
latestEventId: string | null,
|
|
58
34
|
) {
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
this.
|
|
35
|
+
if (latestEventId !== null) {
|
|
36
|
+
this.latestEventId = latestEventId;
|
|
37
|
+
}
|
|
38
|
+
this.logger = specializedEventManager.getLogger();
|
|
63
39
|
}
|
|
64
40
|
|
|
65
|
-
|
|
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
|
-
|
|
70
|
-
this.
|
|
71
|
-
|
|
72
|
-
|
|
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
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
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 {
|
|
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
|
|
23
|
-
private
|
|
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,
|
|
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.
|
|
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
|
-
*
|
|
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
|
|
46
|
-
|
|
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
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
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
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
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.
|
|
77
|
+
numberOfVolumeSubscriptions: Object.keys(this.volumeEventManagers).length,
|
|
100
78
|
});
|
|
101
79
|
}
|
|
102
80
|
|
|
103
|
-
private createVolumeEventManager(volumeId: string
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
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
|
-
|
|
109
|
-
|
|
93
|
+
await eventManager.start();
|
|
94
|
+
this.volumeEventManagers[volumeId] = eventManager;
|
|
95
|
+
return eventManager;
|
|
110
96
|
}
|
|
111
97
|
|
|
112
|
-
|
|
113
|
-
|
|
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 = (
|
|
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
|
|
16
|
-
|
|
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
|
|
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
|
|
28
|
-
type:
|
|
44
|
+
export type NodeEvent = {
|
|
45
|
+
type: NodeCruEventType,
|
|
29
46
|
nodeUid: string,
|
|
30
47
|
parentNodeUid?: string,
|
|
31
48
|
isTrashed: boolean,
|
|
32
49
|
isShared: boolean,
|
|
33
|
-
|
|
50
|
+
treeEventScopeId: string,
|
|
51
|
+
eventId: string,
|
|
34
52
|
} | {
|
|
35
53
|
type: DriveEventType.NodeDeleted,
|
|
36
54
|
nodeUid: string,
|
|
37
55
|
parentNodeUid?: string,
|
|
38
|
-
|
|
39
|
-
|
|
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
|
-
|
|
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
|
}
|