@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,31 +1,32 @@
|
|
|
1
|
-
import { Logger } from "../../interface";
|
|
2
1
|
import { DriveAPIService, drivePaths, corePaths } from "../apiService";
|
|
3
2
|
import { makeNodeUid } from "../uids";
|
|
4
|
-
import {
|
|
3
|
+
import { DriveEventsListWithStatus, DriveEvent, DriveEventType, NodeEvent, NodeEventType } from "./interface";
|
|
5
4
|
|
|
6
5
|
type GetCoreLatestEventResponse = corePaths['/core/{_version}/events/latest']['get']['responses']['200']['content']['application/json'];
|
|
7
6
|
type GetCoreEventResponse = corePaths['/core/{_version}/events/{id}']['get']['responses']['200']['content']['application/json'];
|
|
8
7
|
|
|
9
8
|
type GetVolumeLatestEventResponse = drivePaths['/drive/volumes/{volumeID}/events/latest']['get']['responses']['200']['content']['application/json'];
|
|
10
|
-
type
|
|
9
|
+
type GetVolumeEventResponse = drivePaths['/drive/v2/volumes/{volumeID}/events/{eventID}']['get']['responses']['200']['content']['application/json'];
|
|
11
10
|
|
|
12
|
-
|
|
11
|
+
interface VolumeEventTypeMap {
|
|
12
|
+
[key: number]: NodeEventType,
|
|
13
|
+
}
|
|
14
|
+
const VOLUME_EVENT_TYPE_MAP: VolumeEventTypeMap = {
|
|
13
15
|
0: DriveEventType.NodeDeleted,
|
|
14
16
|
1: DriveEventType.NodeCreated,
|
|
15
17
|
2: DriveEventType.NodeUpdated,
|
|
16
|
-
3: DriveEventType.
|
|
18
|
+
3: DriveEventType.NodeUpdated,
|
|
17
19
|
}
|
|
18
20
|
|
|
19
21
|
/**
|
|
20
22
|
* Provides API communication for fetching events.
|
|
21
|
-
*
|
|
23
|
+
*
|
|
22
24
|
* The service is responsible for transforming local objects to API payloads
|
|
23
25
|
* and vice versa. It should not contain any business logic.
|
|
24
26
|
*/
|
|
25
27
|
export class EventsAPIService {
|
|
26
|
-
constructor(private apiService: DriveAPIService
|
|
28
|
+
constructor(private apiService: DriveAPIService) {
|
|
27
29
|
this.apiService = apiService;
|
|
28
|
-
this.logger = logger;
|
|
29
30
|
}
|
|
30
31
|
|
|
31
32
|
async getCoreLatestEventId(): Promise<string> {
|
|
@@ -33,17 +34,19 @@ export class EventsAPIService {
|
|
|
33
34
|
return result.EventID as string;
|
|
34
35
|
}
|
|
35
36
|
|
|
36
|
-
async getCoreEvents(eventId: string): Promise<
|
|
37
|
-
// TODO: Switch to v6 endpoint
|
|
37
|
+
async getCoreEvents(eventId: string): Promise<DriveEventsListWithStatus> {
|
|
38
|
+
// TODO: Switch to v6 endpoint?
|
|
38
39
|
const result = await this.apiService.get<GetCoreEventResponse>(`core/v5/events/${eventId}`);
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
40
|
+
// in core/v5/events, refresh is always all apps, value 255
|
|
41
|
+
const refresh = result.Refresh > 0;
|
|
42
|
+
const events: DriveEvent[] = (refresh || result.DriveShareRefresh?.Action === 2) ? [{
|
|
43
|
+
type: DriveEventType.SharedWithMeUpdated,
|
|
44
|
+
eventId: result.EventID,
|
|
45
|
+
treeEventScopeId: 'core',
|
|
46
|
+
}] : [];
|
|
44
47
|
|
|
45
48
|
return {
|
|
46
|
-
|
|
49
|
+
latestEventId: result.EventID,
|
|
47
50
|
more: result.More === 1,
|
|
48
51
|
refresh: result.Refresh === 1,
|
|
49
52
|
events,
|
|
@@ -55,31 +58,25 @@ export class EventsAPIService {
|
|
|
55
58
|
return result.EventID;
|
|
56
59
|
}
|
|
57
60
|
|
|
58
|
-
async getVolumeEvents(volumeId: string, eventId: string
|
|
59
|
-
const result = await this.apiService.get<
|
|
61
|
+
async getVolumeEvents(volumeId: string, eventId: string): Promise<DriveEventsListWithStatus> {
|
|
62
|
+
const result = await this.apiService.get<GetVolumeEventResponse>(`drive/v2/volumes/${volumeId}/events/${eventId}`);
|
|
60
63
|
return {
|
|
61
|
-
|
|
64
|
+
latestEventId: result.EventID,
|
|
62
65
|
more: result.More,
|
|
63
66
|
refresh: result.Refresh,
|
|
64
|
-
events: result.Events.map((event):
|
|
67
|
+
events: result.Events.map((event): NodeEvent => {
|
|
65
68
|
const type = VOLUME_EVENT_TYPE_MAP[event.EventType];
|
|
66
69
|
const uids = {
|
|
67
70
|
nodeUid: makeNodeUid(volumeId, event.Link.LinkID),
|
|
68
71
|
parentNodeUid: makeNodeUid(volumeId, event.Link.ParentLinkID as string),
|
|
69
72
|
}
|
|
70
|
-
// VOLUME_EVENT_TYPE_MAP will never return this event type.
|
|
71
|
-
// It is here to satisfy the type checker. It is safe to do.
|
|
72
|
-
if (type === DriveEventType.ShareWithMeUpdated) {
|
|
73
|
-
return {
|
|
74
|
-
type,
|
|
75
|
-
};
|
|
76
|
-
}
|
|
77
73
|
return {
|
|
78
74
|
type,
|
|
79
75
|
...uids,
|
|
80
76
|
isTrashed: event.Link.IsTrashed,
|
|
81
77
|
isShared: event.Link.IsShared,
|
|
82
|
-
|
|
78
|
+
eventId: event.EventID,
|
|
79
|
+
treeEventScopeId: volumeId,
|
|
83
80
|
};
|
|
84
81
|
}),
|
|
85
82
|
};
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { getMockLogger } from "../../tests/logger";
|
|
2
|
+
import { EventsAPIService } from "./apiService";
|
|
3
|
+
import { DriveEvent, DriveEventsListWithStatus, DriveEventType } from "./interface";
|
|
4
|
+
import { CoreEventManager } from "./coreEventManager";
|
|
5
|
+
|
|
6
|
+
describe("CoreEventManager", () => {
|
|
7
|
+
let mockApiService: jest.Mocked<EventsAPIService>;
|
|
8
|
+
let coreEventManager: CoreEventManager;
|
|
9
|
+
const mockLogger = getMockLogger();
|
|
10
|
+
|
|
11
|
+
beforeEach(() => {
|
|
12
|
+
// Reset mocks
|
|
13
|
+
jest.clearAllMocks();
|
|
14
|
+
|
|
15
|
+
mockApiService = {
|
|
16
|
+
getCoreLatestEventId: jest.fn(),
|
|
17
|
+
getCoreEvents: jest.fn(),
|
|
18
|
+
getVolumeLatestEventId: jest.fn(),
|
|
19
|
+
getVolumeEvents: jest.fn(),
|
|
20
|
+
} as unknown as jest.Mocked<EventsAPIService>;
|
|
21
|
+
|
|
22
|
+
coreEventManager = new CoreEventManager(mockLogger, mockApiService);
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
describe("getLatestEventId", () => {
|
|
26
|
+
it("should return the latest event ID from API service", async () => {
|
|
27
|
+
const expectedEventId = "event-123";
|
|
28
|
+
mockApiService.getCoreLatestEventId.mockResolvedValue(expectedEventId);
|
|
29
|
+
|
|
30
|
+
const result = await coreEventManager.getLatestEventId();
|
|
31
|
+
|
|
32
|
+
expect(result).toBe(expectedEventId);
|
|
33
|
+
expect(mockApiService.getCoreLatestEventId).toHaveBeenCalledTimes(1);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it("should handle API service errors", async () => {
|
|
37
|
+
const error = new Error("API error");
|
|
38
|
+
mockApiService.getCoreLatestEventId.mockRejectedValue(error);
|
|
39
|
+
|
|
40
|
+
await expect(coreEventManager.getLatestEventId()).rejects.toThrow("API error");
|
|
41
|
+
expect(mockApiService.getCoreLatestEventId).toHaveBeenCalledTimes(1);
|
|
42
|
+
});
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
describe("getEvents", () => {
|
|
46
|
+
const eventId = "event1";
|
|
47
|
+
const latestEventId = "event2";
|
|
48
|
+
|
|
49
|
+
it("should yield ShareWithMeUpdated event when refresh is true", async () => {
|
|
50
|
+
const mockEvents: DriveEventsListWithStatus = {
|
|
51
|
+
latestEventId,
|
|
52
|
+
more: false,
|
|
53
|
+
refresh: true,
|
|
54
|
+
events: [],
|
|
55
|
+
};
|
|
56
|
+
mockApiService.getCoreEvents.mockResolvedValue(mockEvents);
|
|
57
|
+
|
|
58
|
+
const events = [];
|
|
59
|
+
for await (const event of coreEventManager.getEvents(eventId)) {
|
|
60
|
+
events.push(event);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
expect(events).toHaveLength(1);
|
|
64
|
+
expect(events[0]).toEqual({
|
|
65
|
+
type: DriveEventType.SharedWithMeUpdated,
|
|
66
|
+
treeEventScopeId: 'core',
|
|
67
|
+
eventId: latestEventId,
|
|
68
|
+
});
|
|
69
|
+
expect(mockApiService.getCoreEvents).toHaveBeenCalledWith(eventId);
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it("should yield all events when there are actual events", async () => {
|
|
73
|
+
const mockEvent1: DriveEvent = {
|
|
74
|
+
type: DriveEventType.SharedWithMeUpdated,
|
|
75
|
+
eventId: "event-1",
|
|
76
|
+
treeEventScopeId: 'core',
|
|
77
|
+
};
|
|
78
|
+
const mockEvent2: DriveEvent = {
|
|
79
|
+
type: DriveEventType.SharedWithMeUpdated,
|
|
80
|
+
eventId: "event-2",
|
|
81
|
+
treeEventScopeId: 'core',
|
|
82
|
+
};
|
|
83
|
+
const mockEvents: DriveEventsListWithStatus = {
|
|
84
|
+
latestEventId,
|
|
85
|
+
more: false,
|
|
86
|
+
refresh: false,
|
|
87
|
+
events: [mockEvent1, mockEvent2],
|
|
88
|
+
};
|
|
89
|
+
mockApiService.getCoreEvents.mockResolvedValue(mockEvents);
|
|
90
|
+
|
|
91
|
+
const events = [];
|
|
92
|
+
for await (const event of coreEventManager.getEvents(eventId)) {
|
|
93
|
+
events.push(event);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
expect(events).toHaveLength(2);
|
|
97
|
+
expect(events[0]).toEqual(mockEvent1);
|
|
98
|
+
expect(events[1]).toEqual(mockEvent2);
|
|
99
|
+
});
|
|
100
|
+
});
|
|
101
|
+
});
|
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
import { Logger } from "../../interface";
|
|
2
2
|
import { LoggerWithPrefix } from "../../telemetry";
|
|
3
3
|
import { EventsAPIService } from "./apiService";
|
|
4
|
-
import {
|
|
5
|
-
import { DriveEvent, DriveEventType } from "./interface";
|
|
6
|
-
import { EventManager } from "./eventManager";
|
|
4
|
+
import { DriveEvent, DriveEventType, EventManagerInterface } from "./interface";
|
|
7
5
|
|
|
8
6
|
/**
|
|
9
7
|
* Combines API and event manager to provide a service for listening to
|
|
@@ -11,59 +9,36 @@ import { EventManager } from "./eventManager";
|
|
|
11
9
|
* At this moment, Drive listenes only to shares with me updates from core
|
|
12
10
|
* events. Such even indicates that user was invited to the new share or
|
|
13
11
|
* that user's membership was removed from existing one and lost access.
|
|
14
|
-
*
|
|
12
|
+
*
|
|
15
13
|
* The client might be already using own core events, thus this service
|
|
16
14
|
* is here only in case the client is not connected to the Proton services
|
|
17
15
|
* with own implementation.
|
|
18
16
|
*/
|
|
19
|
-
export class CoreEventManager {
|
|
20
|
-
private
|
|
21
|
-
|
|
22
|
-
constructor(logger: Logger, private apiService: EventsAPIService, private cache: EventsCache) {
|
|
17
|
+
export class CoreEventManager implements EventManagerInterface<DriveEvent> {
|
|
18
|
+
constructor(private logger: Logger, private apiService: EventsAPIService) {
|
|
23
19
|
this.apiService = apiService;
|
|
24
20
|
|
|
25
|
-
this.
|
|
26
|
-
new LoggerWithPrefix(logger, `core`),
|
|
27
|
-
() => this.getLastEventId(),
|
|
28
|
-
(eventId) => this.apiService.getCoreEvents(eventId),
|
|
29
|
-
(lastEventId) => this.cache.setLastEventId('core', {
|
|
30
|
-
lastEventId,
|
|
31
|
-
pollingIntervalInSeconds: this.manager.pollingIntervalInSeconds,
|
|
32
|
-
isOwnVolume: false,
|
|
33
|
-
}),
|
|
34
|
-
);
|
|
21
|
+
this.logger = new LoggerWithPrefix(logger, `core`);
|
|
35
22
|
}
|
|
36
23
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
if (lastEventId) {
|
|
40
|
-
return lastEventId;
|
|
41
|
-
}
|
|
42
|
-
return this.apiService.getCoreLatestEventId();
|
|
24
|
+
async getLatestEventId(): Promise<string> {
|
|
25
|
+
return await this.apiService.getCoreLatestEventId();
|
|
43
26
|
}
|
|
44
27
|
|
|
45
|
-
async
|
|
46
|
-
await this.
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
28
|
+
async * getEvents(eventId: string): AsyncIterable<DriveEvent> {
|
|
29
|
+
const events = await this.apiService.getCoreEvents(eventId);
|
|
30
|
+
if (events.events.length === 0 && events.latestEventId !== eventId) {
|
|
31
|
+
yield {
|
|
32
|
+
type: DriveEventType.SharedWithMeUpdated,
|
|
33
|
+
treeEventScopeId: 'core',
|
|
34
|
+
eventId: events.latestEventId,
|
|
35
|
+
};
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
yield* events.events;
|
|
51
39
|
}
|
|
52
40
|
|
|
53
|
-
|
|
54
|
-
this.
|
|
55
|
-
if (events) {
|
|
56
|
-
await callback(events);
|
|
57
|
-
}
|
|
58
|
-
if (fullRefresh) {
|
|
59
|
-
// Because only updates about shares that are shared with me
|
|
60
|
-
// are listened to from core events, in the case of core full
|
|
61
|
-
// refresh, we don't have to refresh anything more than this
|
|
62
|
-
// one specific event.
|
|
63
|
-
await callback([{
|
|
64
|
-
type: DriveEventType.ShareWithMeUpdated,
|
|
65
|
-
}]);
|
|
66
|
-
}
|
|
67
|
-
});
|
|
41
|
+
getLogger(): Logger {
|
|
42
|
+
return this.logger;
|
|
68
43
|
}
|
|
69
44
|
}
|
|
@@ -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
|
});
|