@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,13 +1,16 @@
|
|
|
1
1
|
import { getMockLogger } from "../../tests/logger";
|
|
2
2
|
import { DriveEvent, DriveEventType } from "../events";
|
|
3
|
-
import { NodesService, SharingType } from "./interface";
|
|
4
3
|
import { SharingCache } from "./cache";
|
|
5
|
-
import { handleSharedByMeNodes, handleSharedWithMeNodes } from "./events";
|
|
6
4
|
import { SharingAccess } from "./sharingAccess";
|
|
5
|
+
import { SharingEventHandler } from "./events";
|
|
6
|
+
import { SharesManager } from "../shares/manager";
|
|
7
|
+
|
|
8
|
+
// FIXME: test tree_refresh and tree_remove
|
|
7
9
|
|
|
8
10
|
describe("handleSharedByMeNodes", () => {
|
|
9
11
|
let cache: SharingCache;
|
|
10
|
-
let
|
|
12
|
+
let sharingEventHandler: SharingEventHandler;
|
|
13
|
+
let sharesManager: SharesManager;
|
|
11
14
|
|
|
12
15
|
beforeEach(() => {
|
|
13
16
|
jest.clearAllMocks();
|
|
@@ -17,197 +20,105 @@ describe("handleSharedByMeNodes", () => {
|
|
|
17
20
|
addSharedByMeNodeUid: jest.fn(),
|
|
18
21
|
removeSharedByMeNodeUid: jest.fn(),
|
|
19
22
|
setSharedWithMeNodeUids: jest.fn(),
|
|
23
|
+
getSharedByMeNodeUids: jest.fn().mockResolvedValue(["cachedNodeUid"]),
|
|
20
24
|
};
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
+
sharesManager = {
|
|
26
|
+
isOwnVolume: jest.fn(async (volumeId: string) => volumeId === 'MyVolume1'),
|
|
27
|
+
} as any;
|
|
28
|
+
sharingEventHandler = new SharingEventHandler(getMockLogger(), cache, sharesManager);
|
|
25
29
|
});
|
|
26
30
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
removed: boolean,
|
|
33
|
-
}[] = [
|
|
34
|
-
{
|
|
35
|
-
title: "should add if new own shared node is created",
|
|
36
|
-
existingNodeUids: [],
|
|
37
|
-
event: {
|
|
31
|
+
describe("node events trigger cache update", () => {
|
|
32
|
+
|
|
33
|
+
it("should add if new own shared node is created", async () => {
|
|
34
|
+
const event: DriveEvent = {
|
|
35
|
+
eventId: "1",
|
|
38
36
|
type: DriveEventType.NodeCreated,
|
|
39
|
-
nodeUid: "
|
|
37
|
+
nodeUid: "newNodeUid",
|
|
40
38
|
parentNodeUid: "parentUid",
|
|
41
39
|
isTrashed: false,
|
|
42
40
|
isShared: true,
|
|
43
|
-
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
41
|
+
treeEventScopeId: "MyVolume1",
|
|
42
|
+
};
|
|
43
|
+
await sharingEventHandler.handleDriveEvent(event);
|
|
44
|
+
expect(cache.addSharedByMeNodeUid).toHaveBeenCalledWith("newNodeUid");
|
|
45
|
+
expect(cache.setSharedWithMeNodeUids).not.toHaveBeenCalled();
|
|
46
|
+
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
// FIXME enable when volume ownership is handled
|
|
50
|
+
test.skip("should not add if new shared node is not own", async () => {
|
|
51
|
+
const event: DriveEvent = {
|
|
52
|
+
eventId: "1",
|
|
52
53
|
type: DriveEventType.NodeCreated,
|
|
53
|
-
nodeUid: "
|
|
54
|
+
nodeUid: "newNodeUid",
|
|
54
55
|
parentNodeUid: "parentUid",
|
|
55
56
|
isTrashed: false,
|
|
56
57
|
isShared: true,
|
|
57
|
-
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
58
|
+
treeEventScopeId: "NotOwnVolume",
|
|
59
|
+
};
|
|
60
|
+
await sharingEventHandler.handleDriveEvent(event);
|
|
61
|
+
expect(cache.addSharedByMeNodeUid).not.toHaveBeenCalled();
|
|
62
|
+
expect(cache.setSharedWithMeNodeUids).not.toHaveBeenCalled();
|
|
63
|
+
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
it("should not add if new own node is not shared", async () => {
|
|
67
|
+
const event: DriveEvent = {
|
|
66
68
|
type: DriveEventType.NodeCreated,
|
|
67
|
-
nodeUid: "
|
|
69
|
+
nodeUid: "newNodeUid",
|
|
68
70
|
parentNodeUid: "parentUid",
|
|
69
71
|
isTrashed: false,
|
|
70
72
|
isShared: false,
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
nodeUid: "nodeUid",
|
|
82
|
-
parentNodeUid: "parentUid",
|
|
83
|
-
isTrashed: false,
|
|
84
|
-
isShared: true,
|
|
85
|
-
isOwnVolume: true,
|
|
86
|
-
},
|
|
87
|
-
added: true,
|
|
88
|
-
removed: false,
|
|
89
|
-
},
|
|
90
|
-
{
|
|
91
|
-
title: "should add/update if shared node is updated",
|
|
92
|
-
existingNodeUids: ["nodeUid"],
|
|
93
|
-
event: {
|
|
73
|
+
eventId: "1",
|
|
74
|
+
treeEventScopeId: "MyVolume1",
|
|
75
|
+
};
|
|
76
|
+
await sharingEventHandler.handleDriveEvent(event);
|
|
77
|
+
expect(cache.addSharedByMeNodeUid).not.toHaveBeenCalled();
|
|
78
|
+
expect(cache.setSharedWithMeNodeUids).not.toHaveBeenCalled();
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
it("should add if own node is updated and shared", async () => {
|
|
82
|
+
const event: DriveEvent = {
|
|
94
83
|
type: DriveEventType.NodeUpdated,
|
|
95
|
-
nodeUid: "
|
|
84
|
+
nodeUid: "cachedNodeUid",
|
|
96
85
|
parentNodeUid: "parentUid",
|
|
97
86
|
isTrashed: false,
|
|
98
87
|
isShared: true,
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
nodeUid: "nodeUid",
|
|
110
|
-
parentNodeUid: "parentUid",
|
|
111
|
-
isTrashed: false,
|
|
112
|
-
isShared: false,
|
|
113
|
-
isOwnVolume: true,
|
|
114
|
-
},
|
|
115
|
-
added: false,
|
|
116
|
-
removed: true,
|
|
117
|
-
},
|
|
118
|
-
{
|
|
119
|
-
title: "should not remove if non-shared node is updated",
|
|
120
|
-
existingNodeUids: [],
|
|
121
|
-
event: {
|
|
88
|
+
eventId: "1",
|
|
89
|
+
treeEventScopeId: "MyVolume1",
|
|
90
|
+
};
|
|
91
|
+
await sharingEventHandler.handleDriveEvent(event);
|
|
92
|
+
expect(cache.addSharedByMeNodeUid).toHaveBeenCalledWith("cachedNodeUid");
|
|
93
|
+
expect(cache.setSharedWithMeNodeUids).not.toHaveBeenCalled();
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
it("should remove if shared node is un-shared", async () => {
|
|
97
|
+
const event: DriveEvent = {
|
|
122
98
|
type: DriveEventType.NodeUpdated,
|
|
123
|
-
nodeUid: "
|
|
99
|
+
nodeUid: "cachedNodeUid",
|
|
124
100
|
parentNodeUid: "parentUid",
|
|
125
101
|
isTrashed: false,
|
|
126
102
|
isShared: false,
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
title: "should remove if shared node is deleted",
|
|
134
|
-
existingNodeUids: ["nodeUid"],
|
|
135
|
-
event: {
|
|
136
|
-
type: DriveEventType.NodeDeleted,
|
|
137
|
-
nodeUid: "nodeUid",
|
|
138
|
-
parentNodeUid: "parentUid",
|
|
139
|
-
isOwnVolume: true,
|
|
140
|
-
},
|
|
141
|
-
added: false,
|
|
142
|
-
removed: true,
|
|
143
|
-
},
|
|
144
|
-
{
|
|
145
|
-
title: "should not remove if non-shared node is deleted",
|
|
146
|
-
existingNodeUids: [],
|
|
147
|
-
event: {
|
|
148
|
-
type: DriveEventType.NodeDeleted,
|
|
149
|
-
nodeUid: "nodeUid",
|
|
150
|
-
parentNodeUid: "parentUid",
|
|
151
|
-
isOwnVolume: true,
|
|
152
|
-
},
|
|
153
|
-
added: false,
|
|
154
|
-
removed: false,
|
|
155
|
-
},
|
|
156
|
-
];
|
|
157
|
-
|
|
158
|
-
describe("with listeners", () => {
|
|
159
|
-
testCases.map(({ title, existingNodeUids, event, added, removed }) => {
|
|
160
|
-
it(title, async () => {
|
|
161
|
-
cache.getSharedByMeNodeUids = jest.fn().mockResolvedValue(existingNodeUids);
|
|
162
|
-
const listener = jest.fn();
|
|
163
|
-
const listeners = [{ type: SharingType.SharedByMe, callback: listener }];
|
|
164
|
-
|
|
165
|
-
await handleSharedByMeNodes(getMockLogger(), event, cache, listeners, nodesService);
|
|
166
|
-
|
|
167
|
-
if (added) {
|
|
168
|
-
expect(cache.addSharedByMeNodeUid).toHaveBeenCalledWith("nodeUid");
|
|
169
|
-
expect(listener).toHaveBeenCalledWith(expect.objectContaining({ type: 'update', uid: 'nodeUid' }));
|
|
170
|
-
} else {
|
|
171
|
-
expect(cache.addSharedByMeNodeUid).not.toHaveBeenCalled();
|
|
172
|
-
}
|
|
173
|
-
if (removed) {
|
|
174
|
-
expect(cache.removeSharedByMeNodeUid).toHaveBeenCalledWith("nodeUid");
|
|
175
|
-
expect(listener).toHaveBeenCalledWith({ type: 'remove', uid: 'nodeUid' });
|
|
176
|
-
} else {
|
|
177
|
-
expect(cache.removeSharedByMeNodeUid).not.toHaveBeenCalled();
|
|
178
|
-
}
|
|
179
|
-
if (!added && !removed) {
|
|
180
|
-
expect(listener).not.toHaveBeenCalled();
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
expect(cache.setSharedWithMeNodeUids).not.toHaveBeenCalled();
|
|
184
|
-
});
|
|
103
|
+
eventId: "1",
|
|
104
|
+
treeEventScopeId: "MyVolume1",
|
|
105
|
+
};
|
|
106
|
+
await sharingEventHandler.handleDriveEvent(event);
|
|
107
|
+
expect(cache.removeSharedByMeNodeUid).toHaveBeenCalledWith("cachedNodeUid");
|
|
108
|
+
expect(cache.setSharedWithMeNodeUids).not.toHaveBeenCalled();
|
|
185
109
|
});
|
|
186
|
-
});
|
|
187
|
-
|
|
188
|
-
describe("without listeners", () => {
|
|
189
|
-
testCases.map(({ title, existingNodeUids, event, added, removed }) => {
|
|
190
|
-
it(title, async () => {
|
|
191
|
-
cache.getSharedByMeNodeUids = jest.fn().mockResolvedValue(existingNodeUids);
|
|
192
|
-
const listener = jest.fn();
|
|
193
|
-
const listeners = [{ type: SharingType.sharedWithMe, callback: listener }];
|
|
194
|
-
|
|
195
|
-
await handleSharedByMeNodes(getMockLogger(), event, cache, listeners, nodesService);
|
|
196
|
-
|
|
197
|
-
if (added) {
|
|
198
|
-
expect(cache.addSharedByMeNodeUid).toHaveBeenCalledWith("nodeUid");
|
|
199
|
-
} else {
|
|
200
|
-
expect(cache.addSharedByMeNodeUid).not.toHaveBeenCalled();
|
|
201
|
-
}
|
|
202
|
-
if (removed) {
|
|
203
|
-
expect(cache.removeSharedByMeNodeUid).toHaveBeenCalledWith("nodeUid");
|
|
204
|
-
} else {
|
|
205
|
-
expect(cache.removeSharedByMeNodeUid).not.toHaveBeenCalled();
|
|
206
|
-
}
|
|
207
110
|
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
111
|
+
it("should remove if shared node is deleted", async () => {
|
|
112
|
+
const event: DriveEvent = {
|
|
113
|
+
type: DriveEventType.NodeDeleted,
|
|
114
|
+
nodeUid: "cachedNodeUid",
|
|
115
|
+
parentNodeUid: "parentUid",
|
|
116
|
+
eventId: "1",
|
|
117
|
+
treeEventScopeId: "MyVolume1",
|
|
118
|
+
};
|
|
119
|
+
await sharingEventHandler.handleDriveEvent(event);
|
|
120
|
+
expect(cache.removeSharedByMeNodeUid).toHaveBeenCalledWith("cachedNodeUid");
|
|
121
|
+
expect(cache.setSharedWithMeNodeUids).not.toHaveBeenCalled();
|
|
211
122
|
});
|
|
212
123
|
});
|
|
213
124
|
});
|
|
@@ -215,6 +126,7 @@ describe("handleSharedByMeNodes", () => {
|
|
|
215
126
|
describe("handleSharedWithMeNodes", () => {
|
|
216
127
|
let cache: SharingCache;
|
|
217
128
|
let sharingAccess: SharingAccess;
|
|
129
|
+
let sharesManager: SharesManager;
|
|
218
130
|
|
|
219
131
|
beforeEach(() => {
|
|
220
132
|
jest.clearAllMocks();
|
|
@@ -228,41 +140,23 @@ describe("handleSharedWithMeNodes", () => {
|
|
|
228
140
|
sharingAccess = {
|
|
229
141
|
iterateSharedNodesWithMe: jest.fn(),
|
|
230
142
|
};
|
|
143
|
+
sharesManager = {
|
|
144
|
+
isOwnVolume: jest.fn(async (volumeId: string) => volumeId === 'MyVolume1'),
|
|
145
|
+
} as any;
|
|
231
146
|
});
|
|
232
147
|
|
|
233
148
|
it("should only update cache", async () => {
|
|
234
149
|
const event: DriveEvent = {
|
|
235
|
-
type: DriveEventType.
|
|
150
|
+
type: DriveEventType.SharedWithMeUpdated,
|
|
151
|
+
eventId: 'event1',
|
|
152
|
+
treeEventScopeId: 'core',
|
|
236
153
|
};
|
|
237
154
|
|
|
238
|
-
|
|
155
|
+
const sharingEventHandler = new SharingEventHandler(getMockLogger(), cache, sharesManager);
|
|
156
|
+
await sharingEventHandler.handleDriveEvent(event);
|
|
239
157
|
|
|
240
158
|
expect(cache.setSharedWithMeNodeUids).toHaveBeenCalledWith(undefined);
|
|
241
159
|
expect(cache.getSharedWithMeNodeUids).not.toHaveBeenCalled();
|
|
242
160
|
expect(sharingAccess.iterateSharedNodesWithMe).not.toHaveBeenCalled();
|
|
243
161
|
});
|
|
244
|
-
|
|
245
|
-
it("should update cache and notify listener", async () => {
|
|
246
|
-
cache.getSharedWithMeNodeUids = jest.fn().mockResolvedValue(["nodeUid1", "nodeUid4"]);
|
|
247
|
-
sharingAccess.iterateSharedNodesWithMe = jest.fn().mockImplementation(async function* () {
|
|
248
|
-
yield { uid: "nodeUid1", name: { ok: true, value: "name1" } };
|
|
249
|
-
yield { uid: "nodeUid2", name: { ok: true, value: "name2" } };
|
|
250
|
-
yield { uid: "nodeUid3", name: { ok: true, value: "name3" } };
|
|
251
|
-
});
|
|
252
|
-
const listener = jest.fn();
|
|
253
|
-
const event: DriveEvent = {
|
|
254
|
-
type: DriveEventType.ShareWithMeUpdated,
|
|
255
|
-
};
|
|
256
|
-
|
|
257
|
-
await handleSharedWithMeNodes(event, cache, [{ type: SharingType.sharedWithMe, callback: listener }], sharingAccess);
|
|
258
|
-
|
|
259
|
-
expect(cache.setSharedWithMeNodeUids).toHaveBeenCalledWith(undefined);
|
|
260
|
-
expect(cache.getSharedWithMeNodeUids).toHaveBeenCalled();
|
|
261
|
-
expect(sharingAccess.iterateSharedNodesWithMe).toHaveBeenCalled();
|
|
262
|
-
expect(listener).toHaveBeenCalledTimes(4);
|
|
263
|
-
expect(listener).toHaveBeenCalledWith(expect.objectContaining({ type: 'update', uid: 'nodeUid1' }));
|
|
264
|
-
expect(listener).toHaveBeenCalledWith(expect.objectContaining({ type: 'update', uid: 'nodeUid2' }));
|
|
265
|
-
expect(listener).toHaveBeenCalledWith(expect.objectContaining({ type: 'update', uid: 'nodeUid3' }));
|
|
266
|
-
expect(listener).toHaveBeenCalledWith({ type: 'remove', uid: 'nodeUid4' });
|
|
267
|
-
});
|
|
268
162
|
});
|
|
@@ -1,166 +1,52 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import { DriveEventsService, DriveEvent, DriveEventType } from "../events";
|
|
1
|
+
import { Logger } from "../../interface";
|
|
2
|
+
import { DriveEvent, DriveEventType } from "../events";
|
|
4
3
|
import { SharingCache } from "./cache";
|
|
5
|
-
import {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
// it simpler. The cache is smart enough to not do unnecessary
|
|
31
|
-
// requests to the API and refresh on web is rare without
|
|
32
|
-
// persistant cache for now.
|
|
33
|
-
if (fullRefreshVolumeId) {
|
|
34
|
-
await cache.setSharedByMeNodeUids(undefined);
|
|
35
|
-
await cache.setSharedWithMeNodeUids(undefined);
|
|
36
|
-
return
|
|
4
|
+
import { SharesService } from "./interface";
|
|
5
|
+
|
|
6
|
+
export class SharingEventHandler {
|
|
7
|
+
constructor(private logger: Logger, private cache: SharingCache, private shares: SharesService) {
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Update cache and notify listeners accordingly for any updates
|
|
12
|
+
* to nodes that are shared by me.
|
|
13
|
+
*
|
|
14
|
+
* Any node create or update that is being shared, is automatically
|
|
15
|
+
* added to the cache and the listeners are notified about the
|
|
16
|
+
* update of the node.
|
|
17
|
+
*
|
|
18
|
+
* Any node delete or update that is not being shared, and the cache
|
|
19
|
+
* includes the node, is removed from the cache and the listeners are
|
|
20
|
+
* notified about the removal of the node.
|
|
21
|
+
*
|
|
22
|
+
* @throws Only if the client's callback throws.
|
|
23
|
+
*/
|
|
24
|
+
async handleDriveEvent(event: DriveEvent) {
|
|
25
|
+
try {
|
|
26
|
+
if (event.type === DriveEventType.SharedWithMeUpdated) {
|
|
27
|
+
await this.cache.setSharedWithMeNodeUids(undefined);
|
|
28
|
+
return;
|
|
37
29
|
}
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
await handleSharedByMeNodes(logger, event, cache, this.listeners, nodesService);
|
|
41
|
-
await handleSharedWithMeNodes(event, cache, this.listeners, sharingAccess);
|
|
30
|
+
if (!(await this.shares.isOwnVolume(event.treeEventScopeId))) {
|
|
31
|
+
return;
|
|
42
32
|
}
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
this.listeners = this.listeners.filter(listener => listener.callback !== callback);
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
subscribeToSharedNodesWithMe(callback: NodeEventCallback) {
|
|
54
|
-
this.listeners.push({ type: SharingType.sharedWithMe, callback });
|
|
55
|
-
return () => {
|
|
56
|
-
this.listeners = this.listeners.filter(listener => listener.callback !== callback);
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
/**
|
|
62
|
-
* Update cache and notify listeners accordingly for any updates
|
|
63
|
-
* to nodes that are shared by me.
|
|
64
|
-
*
|
|
65
|
-
* Any node create or update that is being shared, is automatically
|
|
66
|
-
* added to the cache and the listeners are notified about the
|
|
67
|
-
* update of the node.
|
|
68
|
-
*
|
|
69
|
-
* Any node delete or update that is not being shared, and the cache
|
|
70
|
-
* includes the node, is removed from the cache and the listeners are
|
|
71
|
-
* notified about the removal of the node.
|
|
72
|
-
*
|
|
73
|
-
* @throws Only if the client's callback throws.
|
|
74
|
-
*/
|
|
75
|
-
export async function handleSharedByMeNodes(logger: Logger, event: DriveEvent, cache: SharingCache, listeners: Listeners, nodesService: NodesService) {
|
|
76
|
-
if (event.type === DriveEventType.ShareWithMeUpdated || !event.isOwnVolume) {
|
|
77
|
-
return;
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
const subscribedListeners = listeners.filter(({ type }) => type === SharingType.SharedByMe);
|
|
81
|
-
|
|
82
|
-
if ([DriveEventType.NodeCreated, DriveEventType.NodeUpdated, DriveEventType.NodeUpdatedMetadata].includes(event.type) && event.isShared) {
|
|
83
|
-
try {
|
|
84
|
-
await cache.addSharedByMeNodeUid(event.nodeUid);
|
|
85
|
-
} catch (error: unknown) {
|
|
86
|
-
logger.error(`Skipping shared by me node cache update`, error);
|
|
87
|
-
}
|
|
88
|
-
if (subscribedListeners.length) {
|
|
89
|
-
let node;
|
|
90
|
-
try {
|
|
91
|
-
node = await nodesService.getNode(event.nodeUid);
|
|
92
|
-
} catch (error: unknown) {
|
|
93
|
-
logger.error(`Skipping shared by me node update event to listener`, error);
|
|
33
|
+
if (event.type === DriveEventType.NodeCreated || event.type == DriveEventType.NodeUpdated) {
|
|
34
|
+
if (event.isShared && !event.isTrashed) {
|
|
35
|
+
await this.cache.addSharedByMeNodeUid(event.nodeUid);
|
|
36
|
+
} else {
|
|
37
|
+
await this.cache.removeSharedByMeNodeUid(event.nodeUid);
|
|
38
|
+
}
|
|
94
39
|
return;
|
|
95
40
|
}
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
if (
|
|
101
|
-
((event.type === DriveEventType.NodeUpdated || event.type === DriveEventType.NodeUpdatedMetadata) && !event.isShared)
|
|
102
|
-
|| event.type === DriveEventType.NodeDeleted
|
|
103
|
-
) {
|
|
104
|
-
let nodeWasShared = false;
|
|
105
|
-
try {
|
|
106
|
-
const cachedNodeUids = await cache.getSharedByMeNodeUids();
|
|
107
|
-
nodeWasShared = cachedNodeUids.includes(event.nodeUid);
|
|
108
|
-
} catch {
|
|
109
|
-
// Cache can be empty.
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
if (nodeWasShared) {
|
|
113
|
-
try {
|
|
114
|
-
await cache.removeSharedByMeNodeUid(event.nodeUid);
|
|
115
|
-
} catch (error: unknown) {
|
|
116
|
-
logger.error(`Skipping shared by me node cache remove`, error);
|
|
41
|
+
if (event.type === DriveEventType.NodeDeleted) {
|
|
42
|
+
await this.cache.removeSharedByMeNodeUid(event.nodeUid);
|
|
43
|
+
return;
|
|
117
44
|
}
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
/**
|
|
124
|
-
* Update cache and notify listeners accordingly for any updates
|
|
125
|
-
* to nodes that are shared with me.
|
|
126
|
-
*
|
|
127
|
-
* There is only one event type that is relevant for shared with me
|
|
128
|
-
* nodes, which is the ShareWithMeUpdated event. The event is triggered
|
|
129
|
-
* when the list of shared with me nodes is updated.
|
|
130
|
-
*
|
|
131
|
-
* The cache is cleared and re-populated fully when the client
|
|
132
|
-
* requests the list of shared with me, or is actively listening.
|
|
133
|
-
*
|
|
134
|
-
* If the client listenes to shared with me updates, the client receives
|
|
135
|
-
* update to the full list of shared with me nodes, including remove
|
|
136
|
-
* updates for nodes that are no longer shared with me, but was before.
|
|
137
|
-
*
|
|
138
|
-
* @throws Only if the client's callback throws.
|
|
139
|
-
*/
|
|
140
|
-
export async function handleSharedWithMeNodes(event: DriveEvent, cache: SharingCache, listeners: Listeners, sharingAccess: SharingAccess) {
|
|
141
|
-
if (event.type !== DriveEventType.ShareWithMeUpdated) {
|
|
142
|
-
return;
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
let cachedNodeUids: string[] = [];
|
|
146
|
-
const subscribedListeners = listeners.filter(({ type }) => type === SharingType.sharedWithMe);
|
|
147
|
-
if (subscribedListeners.length) {
|
|
148
|
-
cachedNodeUids = await cache.getSharedWithMeNodeUids();
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
// Clearing the cache must be first, sharingAccess is no-op if cache is set.
|
|
152
|
-
await cache.setSharedWithMeNodeUids(undefined);
|
|
153
|
-
|
|
154
|
-
if (subscribedListeners.length) {
|
|
155
|
-
const nodeUids = [];
|
|
156
|
-
for await (const node of sharingAccess.iterateSharedNodesWithMe()) {
|
|
157
|
-
nodeUids.push(node.uid);
|
|
158
|
-
subscribedListeners.forEach(({ callback }) => callback({ type: 'update', uid: node.uid, node: convertInternalNode(node) }));
|
|
159
|
-
}
|
|
160
|
-
for (const nodeUid of cachedNodeUids) {
|
|
161
|
-
if (!nodeUids.includes(nodeUid)) {
|
|
162
|
-
subscribedListeners.forEach(({ callback }) => callback({ type: 'remove', uid: nodeUid }));
|
|
45
|
+
if (event.type === DriveEventType.TreeRefresh || event.type === DriveEventType.TreeRemove) {
|
|
46
|
+
await this.cache.setSharedWithMeNodeUids(undefined);
|
|
163
47
|
}
|
|
48
|
+
} catch (error: unknown) {
|
|
49
|
+
this.logger.error(`Skipping shared by me node cache update`, error);
|
|
164
50
|
}
|
|
165
51
|
}
|
|
166
52
|
}
|
|
@@ -1,18 +1,17 @@
|
|
|
1
1
|
import { ProtonDriveAccount, ProtonDriveEntitiesCache, ProtonDriveTelemetry } from "../../interface";
|
|
2
2
|
import { DriveCrypto } from '../../crypto';
|
|
3
3
|
import { DriveAPIService } from "../apiService";
|
|
4
|
-
import { DriveEventsService } from "../events";
|
|
5
4
|
import { SharingAPIService } from "./apiService";
|
|
6
5
|
import { SharingCache } from "./cache";
|
|
7
6
|
import { SharingCryptoService } from "./cryptoService";
|
|
8
|
-
import { SharingEvents } from "./events";
|
|
9
7
|
import { SharingAccess } from "./sharingAccess";
|
|
10
8
|
import { SharingManagement } from "./sharingManagement";
|
|
11
|
-
import { SharesService, NodesService
|
|
9
|
+
import { SharesService, NodesService } from "./interface";
|
|
10
|
+
import { SharingEventHandler } from "./events";
|
|
12
11
|
|
|
13
12
|
/**
|
|
14
13
|
* Provides facade for the whole sharing module.
|
|
15
|
-
*
|
|
14
|
+
*
|
|
16
15
|
* The sharing module is responsible for handling invitations, bookmarks,
|
|
17
16
|
* standard shares, listing shared nodes, etc. It includes API communication,
|
|
18
17
|
* encryption, decryption, caching, and event handling.
|
|
@@ -23,21 +22,19 @@ export function initSharingModule(
|
|
|
23
22
|
driveEntitiesCache: ProtonDriveEntitiesCache,
|
|
24
23
|
account: ProtonDriveAccount,
|
|
25
24
|
crypto: DriveCrypto,
|
|
26
|
-
driveEvents: DriveEventsService,
|
|
27
25
|
sharesService: SharesService,
|
|
28
26
|
nodesService: NodesService,
|
|
29
|
-
nodesEvents: NodesEvents,
|
|
30
27
|
) {
|
|
31
28
|
const api = new SharingAPIService(telemetry.getLogger('sharing-api'), apiService);
|
|
32
29
|
const cache = new SharingCache(driveEntitiesCache);
|
|
33
30
|
const cryptoService = new SharingCryptoService(telemetry, crypto, account, sharesService);
|
|
34
31
|
const sharingAccess = new SharingAccess(api, cache, cryptoService, sharesService, nodesService);
|
|
35
|
-
const
|
|
36
|
-
const
|
|
32
|
+
const sharingManagement = new SharingManagement(telemetry.getLogger('sharing'), api, cryptoService, account, sharesService, nodesService);
|
|
33
|
+
const sharingEventHandler = new SharingEventHandler(telemetry.getLogger('sharing-event-handler'), cache, sharesService);
|
|
37
34
|
|
|
38
35
|
return {
|
|
39
36
|
access: sharingAccess,
|
|
40
|
-
|
|
37
|
+
eventHandler: sharingEventHandler,
|
|
41
38
|
management: sharingManagement,
|
|
42
39
|
};
|
|
43
40
|
}
|
|
@@ -21,7 +21,7 @@ export interface EncryptedInvitationRequest {
|
|
|
21
21
|
|
|
22
22
|
/**
|
|
23
23
|
* Internal interface of existing invitation on the API.
|
|
24
|
-
*
|
|
24
|
+
*
|
|
25
25
|
* This interface is used only for managing the invitations. For listing
|
|
26
26
|
* invitations with node metadata, see `EncryptedInvitationWithNode`.
|
|
27
27
|
*/
|
|
@@ -32,7 +32,7 @@ export interface EncryptedInvitation extends EncryptedInvitationRequest {
|
|
|
32
32
|
|
|
33
33
|
/**
|
|
34
34
|
* Internal interface of existing invitation with the share and node metadata.
|
|
35
|
-
*
|
|
35
|
+
*
|
|
36
36
|
* Invitation with node is used for listing shared nodes with me, so it includes
|
|
37
37
|
* what is being shared as well.
|
|
38
38
|
*/
|
|
@@ -116,6 +116,7 @@ export interface EncryptedPublicLink {
|
|
|
116
116
|
flags: number,
|
|
117
117
|
creatorEmail: string,
|
|
118
118
|
publicUrl: string,
|
|
119
|
+
numberOfInitializedDownloads: number;
|
|
119
120
|
armoredUrlPassword: string,
|
|
120
121
|
urlPasswordSalt: string,
|
|
121
122
|
base64SharePassphraseKeyPacket: string,
|
|
@@ -152,6 +153,7 @@ export interface SharesService {
|
|
|
152
153
|
addressId: string,
|
|
153
154
|
addressKey: PrivateKey,
|
|
154
155
|
}>,
|
|
156
|
+
isOwnVolume(volumeId: string): Promise<boolean>;
|
|
155
157
|
}
|
|
156
158
|
|
|
157
159
|
/**
|
|
@@ -171,8 +173,10 @@ export interface NodesService {
|
|
|
171
173
|
addressKey: PrivateKey,
|
|
172
174
|
}>,
|
|
173
175
|
iterateNodes(nodeUids: string[], signal?: AbortSignal): AsyncGenerator<DecryptedNode | MissingNode>;
|
|
176
|
+
notifyNodeChanged(nodeUid: string): Promise<void>;
|
|
174
177
|
}
|
|
175
178
|
|
|
179
|
+
// TODO I think this can be removed
|
|
176
180
|
/**
|
|
177
181
|
* Interface describing the dependencies to the nodes module.
|
|
178
182
|
*/
|