@protontech/drive-sdk 0.0.12 → 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 +8 -3
- package/dist/errors.js +11 -4
- 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 +9 -5
- package/dist/interface/index.js +2 -1
- package/dist/interface/index.js.map +1 -1
- package/dist/interface/nodes.d.ts +21 -1
- package/dist/interface/nodes.js +11 -0
- package/dist/interface/nodes.js.map +1 -1
- package/dist/interface/sharing.d.ts +1 -0
- package/dist/interface/upload.d.ts +57 -3
- package/dist/internal/apiService/driveTypes.d.ts +1341 -465
- package/dist/internal/apiService/errors.js +2 -2
- package/dist/internal/apiService/errors.js.map +1 -1
- package/dist/internal/apiService/transformers.js +2 -0
- package/dist/internal/apiService/transformers.js.map +1 -1
- package/dist/internal/asyncIteratorMap.d.ts +15 -0
- package/dist/internal/asyncIteratorMap.js +59 -0
- package/dist/internal/asyncIteratorMap.js.map +1 -0
- package/dist/internal/asyncIteratorMap.test.js +120 -0
- package/dist/internal/asyncIteratorMap.test.js.map +1 -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.d.ts +1 -0
- 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/apiService.d.ts +2 -2
- package/dist/internal/nodes/apiService.js +16 -6
- package/dist/internal/nodes/apiService.js.map +1 -1
- package/dist/internal/nodes/apiService.test.js +30 -8
- package/dist/internal/nodes/apiService.test.js.map +1 -1
- package/dist/internal/nodes/cache.d.ts +10 -1
- package/dist/internal/nodes/cache.js +18 -0
- package/dist/internal/nodes/cache.js.map +1 -1
- package/dist/internal/nodes/cache.test.js +1 -0
- package/dist/internal/nodes/cache.test.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/cryptoService.test.js +34 -0
- package/dist/internal/nodes/cryptoService.test.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/interface.d.ts +3 -1
- package/dist/internal/nodes/nodesAccess.d.ts +15 -0
- package/dist/internal/nodes/nodesAccess.js +65 -7
- package/dist/internal/nodes/nodesAccess.js.map +1 -1
- package/dist/internal/nodes/nodesAccess.test.js +132 -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 +20 -2
- 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/fileUploader.d.ts +49 -53
- package/dist/internal/upload/fileUploader.js +91 -395
- package/dist/internal/upload/fileUploader.js.map +1 -1
- package/dist/internal/upload/fileUploader.test.js +38 -292
- package/dist/internal/upload/fileUploader.test.js.map +1 -1
- package/dist/internal/upload/index.d.ts +5 -5
- package/dist/internal/upload/index.js +23 -44
- 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 +6 -6
- package/dist/internal/upload/manager.js +32 -66
- package/dist/internal/upload/manager.js.map +1 -1
- package/dist/internal/upload/manager.test.js +100 -117
- package/dist/internal/upload/manager.test.js.map +1 -1
- package/dist/internal/upload/streamUploader.d.ts +62 -0
- package/dist/internal/upload/streamUploader.js +440 -0
- package/dist/internal/upload/streamUploader.js.map +1 -0
- package/dist/internal/upload/streamUploader.test.d.ts +1 -0
- package/dist/internal/upload/streamUploader.test.js +358 -0
- package/dist/internal/upload/streamUploader.test.js.map +1 -0
- package/dist/protonDriveClient.d.ts +22 -165
- package/dist/protonDriveClient.js +27 -191
- package/dist/protonDriveClient.js.map +1 -1
- package/dist/protonDrivePhotosClient.js +3 -2
- package/dist/protonDrivePhotosClient.js.map +1 -1
- package/package.json +4 -4
- 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 +13 -4
- 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 +9 -5
- package/src/interface/nodes.ts +32 -12
- package/src/interface/sharing.ts +1 -0
- package/src/interface/upload.ts +59 -3
- package/src/internal/apiService/driveTypes.ts +1341 -465
- package/src/internal/apiService/errors.ts +3 -2
- package/src/internal/apiService/transformers.ts +2 -0
- package/src/internal/asyncIteratorMap.test.ts +150 -0
- package/src/internal/asyncIteratorMap.ts +64 -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/apiService.test.ts +36 -7
- package/src/internal/nodes/apiService.ts +19 -7
- package/src/internal/nodes/cache.test.ts +1 -0
- package/src/internal/nodes/cache.ts +21 -2
- package/src/internal/nodes/cryptoService.test.ts +38 -0
- 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 +6 -3
- package/src/internal/nodes/nodesAccess.test.ts +133 -91
- package/src/internal/nodes/nodesAccess.ts +70 -8
- 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 +25 -2
- 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/fileUploader.test.ts +46 -376
- package/src/internal/upload/fileUploader.ts +114 -494
- package/src/internal/upload/index.ts +30 -54
- package/src/internal/upload/interface.ts +2 -0
- package/src/internal/upload/manager.test.ts +107 -124
- package/src/internal/upload/manager.ts +48 -80
- package/src/internal/upload/streamUploader.test.ts +468 -0
- package/src/internal/upload/streamUploader.ts +550 -0
- package/src/protonDriveClient.ts +80 -248
- 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 -112
- 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 -135
- /package/dist/{internal/events/cache.test.d.ts → diagnostic/zipGenerators.test.d.ts} +0 -0
- /package/dist/internal/{nodes/index.test.d.ts → asyncIteratorMap.test.d.ts} +0 -0
|
@@ -1,139 +1,252 @@
|
|
|
1
1
|
import { getMockLogger } from "../../tests/logger";
|
|
2
|
-
import { NotFoundAPIError } from "../apiService";
|
|
3
2
|
import { EventManager } from "./eventManager";
|
|
3
|
+
import { DriveEvent, DriveEventType, EventSubscription, UnsubscribeFromEventsSourceError } from "./interface";
|
|
4
4
|
|
|
5
5
|
jest.useFakeTimers();
|
|
6
6
|
|
|
7
|
+
const POLLING_INTERVAL = 1;
|
|
8
|
+
|
|
7
9
|
describe("EventManager", () => {
|
|
8
|
-
let manager: EventManager<
|
|
9
|
-
|
|
10
|
-
const
|
|
10
|
+
let manager: EventManager<DriveEvent>;
|
|
11
|
+
|
|
12
|
+
const getLatestEventIdMock = jest.fn();
|
|
11
13
|
const getEventsMock = jest.fn();
|
|
12
|
-
const updateLatestEventIdMock = jest.fn();
|
|
13
14
|
const listenerMock = jest.fn();
|
|
15
|
+
const mockLogger = getMockLogger();
|
|
16
|
+
const subscriptions: EventSubscription[] = [];
|
|
14
17
|
|
|
15
18
|
beforeEach(() => {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
more: false,
|
|
22
|
-
refresh: false,
|
|
23
|
-
events: ["event1", "event2"],
|
|
24
|
-
}));
|
|
19
|
+
const mockEventManager = {
|
|
20
|
+
getLogger: () => mockLogger,
|
|
21
|
+
getLatestEventId: getLatestEventIdMock,
|
|
22
|
+
getEvents: getEventsMock,
|
|
23
|
+
};
|
|
25
24
|
|
|
26
25
|
manager = new EventManager(
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
updateLatestEventIdMock,
|
|
26
|
+
mockEventManager as any,
|
|
27
|
+
POLLING_INTERVAL,
|
|
28
|
+
null,
|
|
31
29
|
);
|
|
32
|
-
manager.addListener(listenerMock);
|
|
30
|
+
const subscription = manager.addListener(listenerMock);
|
|
31
|
+
subscriptions.push(subscription);
|
|
33
32
|
});
|
|
34
33
|
|
|
35
34
|
afterEach(async () => {
|
|
36
35
|
await manager.stop();
|
|
36
|
+
while (subscriptions.length > 0) {
|
|
37
|
+
const subscription = subscriptions.pop();
|
|
38
|
+
subscription?.dispose();
|
|
39
|
+
}
|
|
40
|
+
jest.clearAllMocks();
|
|
37
41
|
});
|
|
38
42
|
|
|
39
|
-
it("should
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
43
|
+
it("should start polling when started", async () => {
|
|
44
|
+
getLatestEventIdMock.mockResolvedValue('EventId1');
|
|
45
|
+
|
|
46
|
+
const mockEvents: DriveEvent[][] = [
|
|
47
|
+
[{
|
|
48
|
+
type: DriveEventType.FastForward,
|
|
49
|
+
treeEventScopeId: 'volume1',
|
|
50
|
+
eventId: 'EventId2',
|
|
51
|
+
}],
|
|
52
|
+
[{
|
|
53
|
+
type: DriveEventType.FastForward,
|
|
54
|
+
treeEventScopeId: 'volume1',
|
|
55
|
+
eventId: 'EventId3',
|
|
56
|
+
}],
|
|
57
|
+
];
|
|
58
|
+
|
|
59
|
+
getEventsMock.mockImplementationOnce(async function* () {
|
|
60
|
+
yield* mockEvents[0];
|
|
61
|
+
}).mockImplementationOnce(async function* () {
|
|
62
|
+
yield* mockEvents[1];
|
|
63
|
+
}).mockImplementationOnce(async function* () {
|
|
64
|
+
});
|
|
47
65
|
|
|
48
|
-
|
|
49
|
-
await manager.start();
|
|
50
|
-
expect(getLastEventIdMock).toHaveBeenCalledTimes(1);
|
|
66
|
+
expect(getLatestEventIdMock).toHaveBeenCalledTimes(0);
|
|
51
67
|
expect(getEventsMock).toHaveBeenCalledTimes(0);
|
|
52
|
-
|
|
53
|
-
expect(
|
|
54
|
-
|
|
55
|
-
|
|
68
|
+
|
|
69
|
+
expect(await manager.start()).toBeUndefined();
|
|
70
|
+
|
|
71
|
+
expect(getLatestEventIdMock).toHaveBeenCalledTimes(1);
|
|
72
|
+
expect(getEventsMock).toHaveBeenCalledWith('EventId1');
|
|
73
|
+
|
|
56
74
|
await jest.runOnlyPendingTimersAsync();
|
|
57
|
-
expect(getEventsMock).toHaveBeenCalledTimes(
|
|
58
|
-
expect(
|
|
59
|
-
expect(updateLatestEventIdMock).toHaveBeenCalledTimes(1);
|
|
60
|
-
expect(updateLatestEventIdMock).toHaveBeenCalledWith('eventId2');
|
|
75
|
+
expect(getEventsMock).toHaveBeenCalledTimes(2);
|
|
76
|
+
expect(getEventsMock).toHaveBeenCalledWith('EventId2');
|
|
61
77
|
});
|
|
62
78
|
|
|
63
|
-
it("should
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
79
|
+
it("should stop polling when stopped", async () => {
|
|
80
|
+
getLatestEventIdMock.mockResolvedValue('eventId1');
|
|
81
|
+
getEventsMock.mockImplementation(async function* () {
|
|
82
|
+
yield {
|
|
83
|
+
type: DriveEventType.FastForward,
|
|
84
|
+
treeEventScopeId: 'volume1',
|
|
85
|
+
eventId: 'eventId1',
|
|
86
|
+
};
|
|
87
|
+
});
|
|
88
|
+
|
|
70
89
|
await manager.start();
|
|
71
90
|
await jest.runOnlyPendingTimersAsync();
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
expect(
|
|
79
|
-
expect(updateLatestEventIdMock).toHaveBeenCalledWith('eventId3');
|
|
91
|
+
|
|
92
|
+
const callsBeforeStop = getEventsMock.mock.calls.length;
|
|
93
|
+
await manager.stop();
|
|
94
|
+
await jest.runOnlyPendingTimersAsync();
|
|
95
|
+
|
|
96
|
+
// Should not have made additional calls after stopping
|
|
97
|
+
expect(getEventsMock).toHaveBeenCalledTimes(callsBeforeStop);
|
|
80
98
|
});
|
|
81
99
|
|
|
82
|
-
it("should
|
|
83
|
-
|
|
84
|
-
|
|
100
|
+
it("should notify all listeners when getting events", async () => {
|
|
101
|
+
getLatestEventIdMock.mockResolvedValue('eventId1');
|
|
102
|
+
|
|
103
|
+
const mockEvents: DriveEvent[] = [
|
|
104
|
+
{
|
|
105
|
+
type: DriveEventType.NodeCreated,
|
|
106
|
+
nodeUid: 'node1',
|
|
107
|
+
parentNodeUid: 'parent1',
|
|
108
|
+
isTrashed: false,
|
|
109
|
+
isShared: false,
|
|
110
|
+
treeEventScopeId: 'volume1',
|
|
111
|
+
eventId: 'eventId2',
|
|
112
|
+
},
|
|
113
|
+
];
|
|
114
|
+
|
|
115
|
+
getEventsMock.mockImplementationOnce(async function* () {
|
|
116
|
+
yield* mockEvents;
|
|
117
|
+
}).mockImplementation(async function* () {
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
expect(await manager.start()).toBeUndefined();
|
|
85
121
|
await jest.runOnlyPendingTimersAsync();
|
|
86
|
-
expect(getLastEventIdMock).toHaveBeenCalledTimes(2);
|
|
87
122
|
expect(listenerMock).toHaveBeenCalledTimes(1);
|
|
88
|
-
expect(listenerMock).
|
|
89
|
-
expect(updateLatestEventIdMock).toHaveBeenCalledTimes(1);
|
|
90
|
-
expect(updateLatestEventIdMock).toHaveBeenCalledWith('eventId1');
|
|
123
|
+
expect(listenerMock).toHaveBeenNthCalledWith(1, mockEvents[0]);
|
|
91
124
|
});
|
|
92
125
|
|
|
93
|
-
it("should
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
126
|
+
it("should propagate unsubscription errors", async () => {
|
|
127
|
+
getLatestEventIdMock.mockImplementation(() => {
|
|
128
|
+
throw new UnsubscribeFromEventsSourceError("Not found");
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
await expect(manager.start()).rejects.toThrow(UnsubscribeFromEventsSourceError);
|
|
132
|
+
|
|
133
|
+
expect(getLatestEventIdMock).toHaveBeenCalledTimes(1);
|
|
134
|
+
expect(listenerMock).toHaveBeenCalledTimes(0);
|
|
135
|
+
expect(getEventsMock).toHaveBeenCalledTimes(0);
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
it("should continue processing multiple events", async () => {
|
|
139
|
+
getLatestEventIdMock.mockResolvedValue('eventId1');
|
|
140
|
+
|
|
141
|
+
const mockEvents: DriveEvent[] = [
|
|
142
|
+
{
|
|
143
|
+
type: DriveEventType.NodeCreated,
|
|
144
|
+
nodeUid: 'node1',
|
|
145
|
+
parentNodeUid: 'parent1',
|
|
146
|
+
isTrashed: false,
|
|
147
|
+
isShared: false,
|
|
148
|
+
treeEventScopeId: 'volume1',
|
|
149
|
+
eventId: 'eventId2',
|
|
150
|
+
},
|
|
151
|
+
{
|
|
152
|
+
type: DriveEventType.NodeCreated,
|
|
153
|
+
nodeUid: 'node2',
|
|
154
|
+
parentNodeUid: 'parent1',
|
|
155
|
+
isTrashed: false,
|
|
156
|
+
isShared: false,
|
|
157
|
+
treeEventScopeId: 'volume1',
|
|
158
|
+
eventId: 'eventId3',
|
|
99
159
|
}
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
160
|
+
];
|
|
161
|
+
|
|
162
|
+
getEventsMock.mockImplementationOnce(async function* () {
|
|
163
|
+
yield* mockEvents;
|
|
164
|
+
}).mockImplementation(async function* () {
|
|
165
|
+
// Empty generator for subsequent calls
|
|
106
166
|
});
|
|
167
|
+
|
|
107
168
|
await manager.start();
|
|
108
|
-
|
|
169
|
+
await jest.runOnlyPendingTimersAsync();
|
|
109
170
|
|
|
110
|
-
|
|
171
|
+
expect(listenerMock).toHaveBeenCalledTimes(2);
|
|
172
|
+
expect(listenerMock).toHaveBeenNthCalledWith(1, mockEvents[0]);
|
|
173
|
+
expect(listenerMock).toHaveBeenNthCalledWith(2, mockEvents[1]);
|
|
174
|
+
|
|
175
|
+
getEventsMock.mockImplementationOnce(async function* () {
|
|
176
|
+
yield* mockEvents;
|
|
177
|
+
})
|
|
111
178
|
await jest.runOnlyPendingTimersAsync();
|
|
112
|
-
expect(listenerMock).toHaveBeenCalledTimes(
|
|
113
|
-
expect(
|
|
179
|
+
expect(listenerMock).toHaveBeenCalledTimes(4);
|
|
180
|
+
expect(listenerMock).toHaveBeenNthCalledWith(1, mockEvents[0]);
|
|
181
|
+
expect(listenerMock).toHaveBeenNthCalledWith(2, mockEvents[1]);
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
it("should retry on error with exponential backoff", async () => {
|
|
185
|
+
getLatestEventIdMock.mockResolvedValue('eventId1');
|
|
186
|
+
|
|
187
|
+
let callCount = 0;
|
|
188
|
+
getEventsMock.mockImplementation(async function* () {
|
|
189
|
+
callCount++;
|
|
190
|
+
if (callCount <= 3) {
|
|
191
|
+
throw new Error("Network error");
|
|
192
|
+
}
|
|
193
|
+
yield {
|
|
194
|
+
type: DriveEventType.FastForward,
|
|
195
|
+
treeEventScopeId: 'volume1',
|
|
196
|
+
eventId: 'eventId3',
|
|
197
|
+
};
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
expect(manager['retryIndex']).toEqual(0);
|
|
201
|
+
|
|
202
|
+
expect(await manager.start()).toBeUndefined();
|
|
203
|
+
expect(getEventsMock).toHaveBeenCalledTimes(1);
|
|
204
|
+
expect(manager['retryIndex']).toEqual(1);
|
|
114
205
|
|
|
115
|
-
// Second failure.
|
|
116
206
|
await jest.runOnlyPendingTimersAsync();
|
|
117
|
-
expect(
|
|
118
|
-
expect(manager
|
|
207
|
+
expect(getEventsMock).toHaveBeenCalledTimes(2);
|
|
208
|
+
expect(manager['retryIndex']).toEqual(2);
|
|
119
209
|
|
|
120
|
-
// Third failure.
|
|
121
210
|
await jest.runOnlyPendingTimersAsync();
|
|
211
|
+
expect(manager['retryIndex']).toEqual(3);
|
|
212
|
+
|
|
122
213
|
expect(listenerMock).toHaveBeenCalledTimes(0);
|
|
123
|
-
expect(manager.nextPollTimeout).toBe(90000);
|
|
124
214
|
|
|
125
|
-
// And now it passes.
|
|
126
215
|
await jest.runOnlyPendingTimersAsync();
|
|
127
216
|
expect(listenerMock).toHaveBeenCalledTimes(1);
|
|
128
|
-
|
|
129
|
-
expect(
|
|
130
|
-
expect(updateLatestEventIdMock).toHaveBeenCalledWith('eventId2');
|
|
217
|
+
// After success, retry index should reset
|
|
218
|
+
expect(manager['retryIndex']).toEqual(0);
|
|
131
219
|
});
|
|
132
220
|
|
|
133
|
-
it("should stop polling", async () => {
|
|
134
|
-
|
|
221
|
+
it("should stop polling when stopped immediately", async () => {
|
|
222
|
+
getLatestEventIdMock.mockResolvedValue('eventId1');
|
|
223
|
+
getEventsMock.mockImplementation(async function* () {
|
|
224
|
+
yield {
|
|
225
|
+
type: DriveEventType.FastForward,
|
|
226
|
+
treeEventScopeId: 'volume1',
|
|
227
|
+
eventId: 'eventId1',
|
|
228
|
+
};
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
expect(await manager.start()).toBeUndefined();
|
|
232
|
+
expect(getEventsMock).toHaveBeenCalledTimes(1);
|
|
135
233
|
await manager.stop();
|
|
136
234
|
await jest.runOnlyPendingTimersAsync();
|
|
137
|
-
|
|
235
|
+
|
|
236
|
+
// getEvents should have been called once during start, but not again after stop
|
|
237
|
+
expect(getEventsMock).toHaveBeenCalledTimes(1);
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
it("should handle empty event streams", async () => {
|
|
241
|
+
getLatestEventIdMock.mockResolvedValue('eventId1');
|
|
242
|
+
|
|
243
|
+
getEventsMock.mockImplementation(async function* () {
|
|
244
|
+
// Empty generator - no events
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
await manager.start();
|
|
248
|
+
await jest.runOnlyPendingTimersAsync();
|
|
249
|
+
|
|
250
|
+
expect(listenerMock).toHaveBeenCalledTimes(0);
|
|
138
251
|
});
|
|
139
252
|
});
|
|
@@ -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
|
}
|