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