@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.
Files changed (234) hide show
  1. package/dist/cache/index.d.ts +1 -0
  2. package/dist/cache/index.js +3 -1
  3. package/dist/cache/index.js.map +1 -1
  4. package/dist/cache/memoryCache.d.ts +1 -1
  5. package/dist/cache/nullCache.d.ts +14 -0
  6. package/dist/cache/nullCache.js +37 -0
  7. package/dist/cache/nullCache.js.map +1 -0
  8. package/dist/config.d.ts +16 -1
  9. package/dist/config.js +1 -1
  10. package/dist/config.js.map +1 -1
  11. package/dist/crypto/openPGPCrypto.js +2 -0
  12. package/dist/crypto/openPGPCrypto.js.map +1 -1
  13. package/dist/diagnostic/eventsGenerator.d.ts +14 -0
  14. package/dist/diagnostic/eventsGenerator.js +49 -0
  15. package/dist/diagnostic/eventsGenerator.js.map +1 -0
  16. package/dist/diagnostic/httpClient.d.ts +16 -0
  17. package/dist/diagnostic/httpClient.js +81 -0
  18. package/dist/diagnostic/httpClient.js.map +1 -0
  19. package/dist/diagnostic/index.d.ts +10 -0
  20. package/dist/diagnostic/index.js +35 -0
  21. package/dist/diagnostic/index.js.map +1 -0
  22. package/dist/diagnostic/integrityVerificationStream.d.ts +21 -0
  23. package/dist/diagnostic/integrityVerificationStream.js +56 -0
  24. package/dist/diagnostic/integrityVerificationStream.js.map +1 -0
  25. package/dist/diagnostic/interface.d.ts +102 -0
  26. package/dist/diagnostic/interface.js +3 -0
  27. package/dist/diagnostic/interface.js.map +1 -0
  28. package/dist/diagnostic/sdkDiagnostic.d.ts +22 -0
  29. package/dist/diagnostic/sdkDiagnostic.js +216 -0
  30. package/dist/diagnostic/sdkDiagnostic.js.map +1 -0
  31. package/dist/diagnostic/sdkDiagnosticFull.d.ts +18 -0
  32. package/dist/diagnostic/sdkDiagnosticFull.js +35 -0
  33. package/dist/diagnostic/sdkDiagnosticFull.js.map +1 -0
  34. package/dist/diagnostic/telemetry.d.ts +25 -0
  35. package/dist/diagnostic/telemetry.js +70 -0
  36. package/dist/diagnostic/telemetry.js.map +1 -0
  37. package/dist/diagnostic/zipGenerators.d.ts +9 -0
  38. package/dist/diagnostic/zipGenerators.js +64 -0
  39. package/dist/diagnostic/zipGenerators.js.map +1 -0
  40. package/dist/diagnostic/zipGenerators.test.js +144 -0
  41. package/dist/diagnostic/zipGenerators.test.js.map +1 -0
  42. package/dist/errors.d.ts +2 -1
  43. package/dist/errors.js +3 -1
  44. package/dist/errors.js.map +1 -1
  45. package/dist/interface/config.d.ts +26 -0
  46. package/dist/interface/config.js +3 -0
  47. package/dist/interface/config.js.map +1 -0
  48. package/dist/interface/download.d.ts +2 -2
  49. package/dist/interface/events.d.ts +60 -20
  50. package/dist/interface/events.js +11 -1
  51. package/dist/interface/events.js.map +1 -1
  52. package/dist/interface/httpClient.d.ts +0 -14
  53. package/dist/interface/index.d.ts +8 -4
  54. package/dist/interface/index.js +2 -1
  55. package/dist/interface/index.js.map +1 -1
  56. package/dist/interface/nodes.d.ts +9 -0
  57. package/dist/interface/nodes.js.map +1 -1
  58. package/dist/interface/sharing.d.ts +1 -0
  59. package/dist/interface/upload.d.ts +6 -0
  60. package/dist/internal/download/apiService.js +32 -31
  61. package/dist/internal/download/apiService.js.map +1 -1
  62. package/dist/internal/download/fileDownloader.d.ts +2 -2
  63. package/dist/internal/download/fileDownloader.js.map +1 -1
  64. package/dist/internal/events/apiService.d.ts +4 -6
  65. package/dist/internal/events/apiService.js +15 -22
  66. package/dist/internal/events/apiService.js.map +1 -1
  67. package/dist/internal/events/coreEventManager.d.ts +7 -10
  68. package/dist/internal/events/coreEventManager.js +19 -36
  69. package/dist/internal/events/coreEventManager.js.map +1 -1
  70. package/dist/internal/events/coreEventManager.test.js +87 -0
  71. package/dist/internal/events/coreEventManager.test.js.map +1 -0
  72. package/dist/internal/events/eventManager.d.ts +11 -36
  73. package/dist/internal/events/eventManager.js +59 -105
  74. package/dist/internal/events/eventManager.js.map +1 -1
  75. package/dist/internal/events/eventManager.test.js +167 -82
  76. package/dist/internal/events/eventManager.test.js.map +1 -1
  77. package/dist/internal/events/index.d.ts +13 -33
  78. package/dist/internal/events/index.js +56 -72
  79. package/dist/internal/events/index.js.map +1 -1
  80. package/dist/internal/events/interface.d.ts +59 -14
  81. package/dist/internal/events/interface.js +13 -3
  82. package/dist/internal/events/interface.js.map +1 -1
  83. package/dist/internal/events/volumeEventManager.d.ts +7 -17
  84. package/dist/internal/events/volumeEventManager.js +58 -45
  85. package/dist/internal/events/volumeEventManager.js.map +1 -1
  86. package/dist/internal/events/volumeEventManager.test.d.ts +1 -0
  87. package/dist/internal/events/volumeEventManager.test.js +203 -0
  88. package/dist/internal/events/volumeEventManager.test.js.map +1 -0
  89. package/dist/internal/nodes/cache.d.ts +10 -1
  90. package/dist/internal/nodes/cache.js +17 -0
  91. package/dist/internal/nodes/cache.js.map +1 -1
  92. package/dist/internal/nodes/cryptoService.d.ts +1 -1
  93. package/dist/internal/nodes/cryptoService.js.map +1 -1
  94. package/dist/internal/nodes/events.d.ts +7 -83
  95. package/dist/internal/nodes/events.js +43 -217
  96. package/dist/internal/nodes/events.js.map +1 -1
  97. package/dist/internal/nodes/events.test.js +27 -277
  98. package/dist/internal/nodes/events.test.js.map +1 -1
  99. package/dist/internal/nodes/index.d.ts +3 -4
  100. package/dist/internal/nodes/index.js +5 -5
  101. package/dist/internal/nodes/index.js.map +1 -1
  102. package/dist/internal/nodes/nodesAccess.d.ts +15 -0
  103. package/dist/internal/nodes/nodesAccess.js +37 -0
  104. package/dist/internal/nodes/nodesAccess.js.map +1 -1
  105. package/dist/internal/nodes/nodesAccess.test.js +131 -93
  106. package/dist/internal/nodes/nodesAccess.test.js.map +1 -1
  107. package/dist/internal/nodes/nodesManagement.d.ts +1 -3
  108. package/dist/internal/nodes/nodesManagement.js +12 -26
  109. package/dist/internal/nodes/nodesManagement.js.map +1 -1
  110. package/dist/internal/nodes/nodesManagement.test.js +35 -14
  111. package/dist/internal/nodes/nodesManagement.test.js.map +1 -1
  112. package/dist/internal/shares/cache.d.ts +2 -0
  113. package/dist/internal/shares/cache.js +2 -0
  114. package/dist/internal/shares/cache.js.map +1 -1
  115. package/dist/internal/shares/manager.d.ts +1 -0
  116. package/dist/internal/shares/manager.js +3 -0
  117. package/dist/internal/shares/manager.js.map +1 -1
  118. package/dist/internal/sharing/apiService.js +1 -0
  119. package/dist/internal/sharing/apiService.js.map +1 -1
  120. package/dist/internal/sharing/cryptoService.js +1 -0
  121. package/dist/internal/sharing/cryptoService.js.map +1 -1
  122. package/dist/internal/sharing/events.d.ts +23 -55
  123. package/dist/internal/sharing/events.js +46 -138
  124. package/dist/internal/sharing/events.js.map +1 -1
  125. package/dist/internal/sharing/events.test.js +77 -180
  126. package/dist/internal/sharing/events.test.js.map +1 -1
  127. package/dist/internal/sharing/index.d.ts +4 -5
  128. package/dist/internal/sharing/index.js +5 -5
  129. package/dist/internal/sharing/index.js.map +1 -1
  130. package/dist/internal/sharing/interface.d.ts +3 -0
  131. package/dist/internal/sharing/sharingManagement.d.ts +2 -3
  132. package/dist/internal/sharing/sharingManagement.js +7 -9
  133. package/dist/internal/sharing/sharingManagement.js.map +1 -1
  134. package/dist/internal/sharing/sharingManagement.test.js +9 -39
  135. package/dist/internal/sharing/sharingManagement.test.js.map +1 -1
  136. package/dist/internal/upload/apiService.d.ts +2 -3
  137. package/dist/internal/upload/apiService.js +7 -4
  138. package/dist/internal/upload/apiService.js.map +1 -1
  139. package/dist/internal/upload/index.d.ts +2 -2
  140. package/dist/internal/upload/index.js +3 -3
  141. package/dist/internal/upload/index.js.map +1 -1
  142. package/dist/internal/upload/interface.d.ts +2 -0
  143. package/dist/internal/upload/manager.d.ts +5 -5
  144. package/dist/internal/upload/manager.js +19 -50
  145. package/dist/internal/upload/manager.js.map +1 -1
  146. package/dist/internal/upload/manager.test.js +68 -44
  147. package/dist/internal/upload/manager.test.js.map +1 -1
  148. package/dist/internal/upload/streamUploader.js +1 -2
  149. package/dist/internal/upload/streamUploader.js.map +1 -1
  150. package/dist/internal/upload/streamUploader.test.js +1 -1
  151. package/dist/internal/upload/streamUploader.test.js.map +1 -1
  152. package/dist/protonDriveClient.d.ts +19 -162
  153. package/dist/protonDriveClient.js +26 -190
  154. package/dist/protonDriveClient.js.map +1 -1
  155. package/dist/protonDrivePhotosClient.js +3 -2
  156. package/dist/protonDrivePhotosClient.js.map +1 -1
  157. package/package.json +3 -3
  158. package/src/cache/index.ts +1 -0
  159. package/src/cache/memoryCache.ts +1 -1
  160. package/src/cache/nullCache.ts +38 -0
  161. package/src/config.ts +17 -2
  162. package/src/crypto/openPGPCrypto.ts +2 -0
  163. package/src/diagnostic/eventsGenerator.ts +48 -0
  164. package/src/diagnostic/httpClient.ts +80 -0
  165. package/src/diagnostic/index.ts +38 -0
  166. package/src/diagnostic/integrityVerificationStream.ts +56 -0
  167. package/src/diagnostic/interface.ts +158 -0
  168. package/src/diagnostic/sdkDiagnostic.ts +238 -0
  169. package/src/diagnostic/sdkDiagnosticFull.ts +40 -0
  170. package/src/diagnostic/telemetry.ts +71 -0
  171. package/src/diagnostic/zipGenerators.test.ts +177 -0
  172. package/src/diagnostic/zipGenerators.ts +70 -0
  173. package/src/errors.ts +4 -1
  174. package/src/interface/config.ts +28 -0
  175. package/src/interface/download.ts +2 -2
  176. package/src/interface/events.ts +66 -21
  177. package/src/interface/httpClient.ts +0 -16
  178. package/src/interface/index.ts +8 -4
  179. package/src/interface/nodes.ts +21 -12
  180. package/src/interface/sharing.ts +1 -0
  181. package/src/interface/upload.ts +6 -0
  182. package/src/internal/download/apiService.ts +11 -8
  183. package/src/internal/download/fileDownloader.ts +2 -2
  184. package/src/internal/events/apiService.ts +25 -28
  185. package/src/internal/events/coreEventManager.test.ts +101 -0
  186. package/src/internal/events/coreEventManager.ts +20 -45
  187. package/src/internal/events/eventManager.test.ts +201 -88
  188. package/src/internal/events/eventManager.ts +69 -115
  189. package/src/internal/events/index.ts +54 -84
  190. package/src/internal/events/interface.ts +70 -15
  191. package/src/internal/events/volumeEventManager.test.ts +243 -0
  192. package/src/internal/events/volumeEventManager.ts +55 -53
  193. package/src/internal/nodes/cache.ts +20 -2
  194. package/src/internal/nodes/cryptoService.ts +1 -1
  195. package/src/internal/nodes/events.test.ts +29 -335
  196. package/src/internal/nodes/events.ts +45 -253
  197. package/src/internal/nodes/index.ts +6 -8
  198. package/src/internal/nodes/interface.ts +2 -2
  199. package/src/internal/nodes/nodesAccess.test.ts +132 -91
  200. package/src/internal/nodes/nodesAccess.ts +40 -1
  201. package/src/internal/nodes/nodesManagement.test.ts +39 -15
  202. package/src/internal/nodes/nodesManagement.ts +12 -30
  203. package/src/internal/shares/cache.ts +4 -2
  204. package/src/internal/shares/manager.ts +9 -5
  205. package/src/internal/sharing/apiService.ts +1 -0
  206. package/src/internal/sharing/cache.ts +1 -1
  207. package/src/internal/sharing/cryptoService.ts +1 -0
  208. package/src/internal/sharing/events.test.ts +89 -195
  209. package/src/internal/sharing/events.ts +42 -156
  210. package/src/internal/sharing/index.ts +6 -9
  211. package/src/internal/sharing/interface.ts +6 -2
  212. package/src/internal/sharing/sharingManagement.test.ts +10 -40
  213. package/src/internal/sharing/sharingManagement.ts +7 -11
  214. package/src/internal/upload/apiService.ts +5 -6
  215. package/src/internal/upload/index.ts +5 -5
  216. package/src/internal/upload/interface.ts +2 -0
  217. package/src/internal/upload/manager.test.ts +75 -45
  218. package/src/internal/upload/manager.ts +24 -54
  219. package/src/internal/upload/streamUploader.test.ts +0 -1
  220. package/src/internal/upload/streamUploader.ts +0 -2
  221. package/src/protonDriveClient.ts +75 -244
  222. package/src/protonDrivePhotosClient.ts +4 -3
  223. package/dist/internal/events/cache.d.ts +0 -28
  224. package/dist/internal/events/cache.js +0 -67
  225. package/dist/internal/events/cache.js.map +0 -1
  226. package/dist/internal/events/cache.test.js +0 -43
  227. package/dist/internal/events/cache.test.js.map +0 -1
  228. package/dist/internal/nodes/index.test.js +0 -114
  229. package/dist/internal/nodes/index.test.js.map +0 -1
  230. package/src/internal/events/cache.test.ts +0 -47
  231. package/src/internal/events/cache.ts +0 -80
  232. package/src/internal/nodes/index.test.ts +0 -137
  233. /package/dist/{internal/events/cache.test.d.ts → diagnostic/zipGenerators.test.d.ts} +0 -0
  234. /package/dist/internal/{nodes/index.test.d.ts → events/coreEventManager.test.d.ts} +0 -0
@@ -22,7 +22,7 @@ describe('nodesAccess', () => {
22
22
  apiService = {
23
23
  getNode: jest.fn(),
24
24
  iterateNodes: jest.fn().mockImplementation(async function* (uids: string[]) {
25
- yield* uids.map((uid => ({ uid, parentUid: 'parentUid' } as EncryptedNode)));
25
+ yield* uids.map((uid => ({ uid, parentUid: 'volumeId~parentNodeId' } as EncryptedNode)));
26
26
  }),
27
27
  iterateChildrenNodeUids: jest.fn(),
28
28
  }
@@ -55,49 +55,51 @@ describe('nodesAccess', () => {
55
55
 
56
56
  describe('getNode', () => {
57
57
  it('should get node from cache', async () => {
58
- const node = { uid: 'nodeId', isStale: false } as DecryptedNode;
58
+ const node = { uid: 'volumeId~nodeId', isStale: false } as DecryptedNode;
59
59
  cache.getNode = jest.fn(() => Promise.resolve(node));
60
60
 
61
- const result = await access.getNode('nodeId');
61
+ const result = await access.getNode('volumeId~nodeId');
62
62
  expect(result).toBe(node);
63
63
  expect(apiService.getNode).not.toHaveBeenCalled();
64
64
  });
65
65
 
66
- it('should get node from API when cahce is stale', async () => {
67
- const encryptedNode = { uid: 'nodeId', parentUid: 'parentUid' } as EncryptedNode;
68
- const decryptedUnparsedNode = { uid: 'nodeId', parentUid: 'parentUid', name: { ok: true, value: 'name' } } as DecryptedUnparsedNode;
66
+ it('should get node from API when cache is stale', async () => {
67
+ const encryptedNode = { uid: 'volumeId~nodeId', parentUid: 'volumeId~parentNodeid' } as EncryptedNode;
68
+ const decryptedUnparsedNode = { uid: 'volumeId~nodeId', parentUid: 'volumeId~parentNodeid', name: { ok: true, value: 'name' } } as DecryptedUnparsedNode;
69
69
  const decryptedNode = {
70
70
  ...decryptedUnparsedNode,
71
71
  name: { ok: true, value: 'name' },
72
72
  isStale: false,
73
73
  activeRevision: undefined,
74
74
  folder: undefined,
75
+ treeEventScopeId: "volumeId",
75
76
  } as DecryptedNode;
76
77
  const decryptedKeys = { key: 'key' } as any as DecryptedNodeKeys;
77
78
 
78
- cache.getNode = jest.fn(() => Promise.resolve({ uid: 'nodeId', isStale: true } as DecryptedNode));
79
+ cache.getNode = jest.fn(() => Promise.resolve({ uid: 'volumeId~nodeId', isStale: true } as DecryptedNode));
79
80
  apiService.getNode = jest.fn(() => Promise.resolve(encryptedNode));
80
81
  cryptoCache.getNodeKeys = jest.fn(() => Promise.resolve({ key: 'parentKey' } as any as DecryptedNodeKeys));
81
82
  cryptoService.decryptNode = jest.fn(() => Promise.resolve({ node: decryptedUnparsedNode, keys: decryptedKeys }));
82
83
 
83
- const result = await access.getNode('nodeId');
84
+ const result = await access.getNode('volumeId~nodeId');
84
85
  expect(result).toEqual(decryptedNode);
85
- expect(apiService.getNode).toHaveBeenCalledWith('nodeId', 'volumeId');
86
- expect(cryptoCache.getNodeKeys).toHaveBeenCalledWith('parentUid');
86
+ expect(apiService.getNode).toHaveBeenCalledWith('volumeId~nodeId', 'volumeId');
87
+ expect(cryptoCache.getNodeKeys).toHaveBeenCalledWith('volumeId~parentNodeid');
87
88
  expect(cryptoService.decryptNode).toHaveBeenCalledWith(encryptedNode, 'parentKey');
88
89
  expect(cache.setNode).toHaveBeenCalledWith(decryptedNode);
89
- expect(cryptoCache.setNodeKeys).toHaveBeenCalledWith('nodeId', decryptedKeys);
90
+ expect(cryptoCache.setNodeKeys).toHaveBeenCalledWith('volumeId~nodeId', decryptedKeys);
90
91
  });
91
92
 
92
93
  it('should get node from API missing cache', async () => {
93
- const encryptedNode = { uid: 'nodeId', parentUid: 'parentUid' } as EncryptedNode;
94
- const decryptedUnparsedNode = { uid: 'nodeId', parentUid: 'parentUid', name: { ok: true, value: 'name' } } as DecryptedUnparsedNode;
94
+ const encryptedNode = { uid: 'volumeId~nodeId', parentUid: 'volumeId~parentNodeid' } as EncryptedNode;
95
+ const decryptedUnparsedNode = { uid: 'volumeId~nodeId', parentUid: 'volumeId~parentNodeid', name: { ok: true, value: 'name' } } as DecryptedUnparsedNode;
95
96
  const decryptedNode = {
96
97
  ...decryptedUnparsedNode,
97
98
  name: { ok: true, value: 'name' },
98
99
  isStale: false,
99
100
  activeRevision: undefined,
100
101
  folder: undefined,
102
+ treeEventScopeId: 'volumeId',
101
103
  } as DecryptedNode;
102
104
  const decryptedKeys = { key: 'key' } as any as DecryptedNodeKeys;
103
105
 
@@ -106,21 +108,22 @@ describe('nodesAccess', () => {
106
108
  cryptoCache.getNodeKeys = jest.fn(() => Promise.resolve({ key: 'parentKey' } as any as DecryptedNodeKeys));
107
109
  cryptoService.decryptNode = jest.fn(() => Promise.resolve({ node: decryptedUnparsedNode, keys: decryptedKeys }));
108
110
 
109
- const result = await access.getNode('nodeId');
111
+ const result = await access.getNode('volumeId~nodeId');
110
112
  expect(result).toEqual(decryptedNode);
111
- expect(apiService.getNode).toHaveBeenCalledWith('nodeId', 'volumeId');
112
- expect(cryptoCache.getNodeKeys).toHaveBeenCalledWith('parentUid');
113
+ expect(apiService.getNode).toHaveBeenCalledWith('volumeId~nodeId', 'volumeId');
114
+ expect(cryptoCache.getNodeKeys).toHaveBeenCalledWith('volumeId~parentNodeid');
113
115
  expect(cryptoService.decryptNode).toHaveBeenCalledWith(encryptedNode, 'parentKey');
114
116
  expect(cache.setNode).toHaveBeenCalledWith(decryptedNode);
115
- expect(cryptoCache.setNodeKeys).toHaveBeenCalledWith('nodeId', decryptedKeys);
117
+ expect(cryptoCache.setNodeKeys).toHaveBeenCalledWith('volumeId~nodeId', decryptedKeys);
116
118
  });
117
119
 
118
120
  it('should validate node name', async () => {
119
- const encryptedNode = { uid: 'nodeId', parentUid: 'parentUid' } as EncryptedNode;
120
- const decryptedUnparsedNode = { uid: 'nodeId', parentUid: 'parentUid', name: { ok: true, value: 'foo/bar' } } as DecryptedUnparsedNode;
121
+ const encryptedNode = { uid: 'volumeId~nodeId', parentUid: 'volumeId~parentNodeid' } as EncryptedNode;
122
+ const decryptedUnparsedNode = { uid: 'volumeId~nodeId', parentUid: 'volumeId~parentNodeid', name: { ok: true, value: 'foo/bar' } } as DecryptedUnparsedNode;
121
123
  const decryptedNode = {
122
124
  ...decryptedUnparsedNode,
123
125
  name: { ok: false, error: { name: 'foo/bar', error: "Name must not contain the character '/'" } },
126
+ treeEventScopeId: 'volumeId',
124
127
  } as DecryptedNode;
125
128
  const decryptedKeys = { key: 'key' } as any as DecryptedNodeKeys;
126
129
 
@@ -129,7 +132,7 @@ describe('nodesAccess', () => {
129
132
  cryptoCache.getNodeKeys = jest.fn(() => Promise.resolve({ key: 'parentKey' } as any as DecryptedNodeKeys));
130
133
  cryptoService.decryptNode = jest.fn(() => Promise.resolve({ node: decryptedUnparsedNode, keys: decryptedKeys }));
131
134
 
132
- const result = await access.getNode('nodeId');
135
+ const result = await access.getNode('volumeId~nodeId');
133
136
  expect(result).toMatchObject(decryptedNode);
134
137
  });
135
138
  });
@@ -144,11 +147,11 @@ describe('nodesAccess', () => {
144
147
  });
145
148
 
146
149
  describe('iterateChildren', () => {
147
- const parentNode = { uid: 'parentUid', isStale: false } as DecryptedNode;
148
- const node1 = { uid: 'node1', isStale: false } as DecryptedNode;
149
- const node2 = { uid: 'node2', isStale: false } as DecryptedNode;
150
- const node3 = { uid: 'node3', isStale: false } as DecryptedNode;
151
- const node4 = { uid: 'node4', isStale: false } as DecryptedNode;
150
+ const parentNode = { uid: 'volumeId~parentNodeid', isStale: false } as DecryptedNode;
151
+ const node1 = { uid: 'volumeId~node1', isStale: false } as DecryptedNode;
152
+ const node2 = { uid: 'volumeId~node2', isStale: false } as DecryptedNode;
153
+ const node3 = { uid: 'volumeId~node3', isStale: false } as DecryptedNode;
154
+ const node4 = { uid: 'volumeId~node4', isStale: false } as DecryptedNode;
152
155
 
153
156
  beforeEach(() => {
154
157
  cache.getNode = jest.fn().mockResolvedValue(parentNode);
@@ -163,7 +166,7 @@ describe('nodesAccess', () => {
163
166
  yield { ok: true, node: node4 };
164
167
  });
165
168
 
166
- const result = await Array.fromAsync(access.iterateFolderChildren('parentUid'));
169
+ const result = await Array.fromAsync(access.iterateFolderChildren('volumeId~parentNodeid'));
167
170
  expect(result).toMatchObject([node1, node2, node3, node4]);
168
171
  expect(apiService.iterateChildrenNodeUids).not.toHaveBeenCalled();
169
172
  expect(apiService.iterateNodes).not.toHaveBeenCalled();
@@ -178,9 +181,9 @@ describe('nodesAccess', () => {
178
181
  yield { ok: true, uid: node4.uid, node: node4 };
179
182
  });
180
183
 
181
- const result = await Array.fromAsync(access.iterateFolderChildren('parentUid'));
184
+ const result = await Array.fromAsync(access.iterateFolderChildren('volumeId~parentNodeid'));
182
185
  expect(result).toMatchObject([node1, node4, node2, node3]);
183
- expect(apiService.iterateNodes).toHaveBeenCalledWith(['node2', 'node3'], 'volumeId', undefined);
186
+ expect(apiService.iterateNodes).toHaveBeenCalledWith([node2.uid, node3.uid], 'volumeId', undefined);
184
187
  expect(cryptoService.decryptNode).toHaveBeenCalledTimes(2);
185
188
  expect(cache.setNode).toHaveBeenCalledTimes(2);
186
189
  expect(cryptoCache.setNodeKeys).toHaveBeenCalledTimes(2);
@@ -188,26 +191,26 @@ describe('nodesAccess', () => {
188
191
 
189
192
  it('should load children uids and serve nodes from cache', async () => {
190
193
  apiService.iterateChildrenNodeUids = jest.fn().mockImplementation(async function* () {
191
- yield 'node1';
192
- yield 'node2';
193
- yield 'node3';
194
- yield 'node4';
194
+ yield node1.uid;
195
+ yield node2.uid;
196
+ yield node3.uid;
197
+ yield node4.uid;
195
198
  });
196
199
  cache.getNode = jest.fn().mockImplementation((uid: string) => ({ uid, isStale: false }));
197
200
 
198
- const result = await Array.fromAsync(access.iterateFolderChildren('parentUid'));
201
+ const result = await Array.fromAsync(access.iterateFolderChildren('volumeId~parentNodeid'));
199
202
  expect(result).toMatchObject([node1, node2, node3, node4]);
200
- expect(apiService.iterateChildrenNodeUids).toHaveBeenCalledWith('parentUid', undefined);
203
+ expect(apiService.iterateChildrenNodeUids).toHaveBeenCalledWith('volumeId~parentNodeid', undefined);
201
204
  expect(apiService.iterateNodes).not.toHaveBeenCalled();
202
- expect(cache.setFolderChildrenLoaded).toHaveBeenCalledWith('parentUid');
205
+ expect(cache.setFolderChildrenLoaded).toHaveBeenCalledWith('volumeId~parentNodeid');
203
206
  });
204
207
 
205
208
  it('should load from API', async () => {
206
209
  apiService.iterateChildrenNodeUids = jest.fn().mockImplementation(async function* () {
207
- yield 'node1';
208
- yield 'node2';
209
- yield 'node3';
210
- yield 'node4';
210
+ yield node1.uid;
211
+ yield node2.uid;
212
+ yield node3.uid;
213
+ yield node4.uid;
211
214
  });
212
215
  cache.getNode = jest.fn().mockImplementation((uid: string) => {
213
216
  if (uid === parentNode.uid) {
@@ -216,21 +219,21 @@ describe('nodesAccess', () => {
216
219
  throw new Error('Entity not found');
217
220
  });
218
221
 
219
- const result = await Array.fromAsync(access.iterateFolderChildren('parentUid'));
222
+ const result = await Array.fromAsync(access.iterateFolderChildren('volumeId~parentNodeid'));
220
223
  expect(result).toMatchObject([node1, node2, node3, node4]);
221
- expect(apiService.iterateChildrenNodeUids).toHaveBeenCalledWith('parentUid', undefined);
222
- expect(apiService.iterateNodes).toHaveBeenCalledWith(['node1', 'node2', 'node3', 'node4'], 'volumeId', undefined);
224
+ expect(apiService.iterateChildrenNodeUids).toHaveBeenCalledWith('volumeId~parentNodeid', undefined);
225
+ expect(apiService.iterateNodes).toHaveBeenCalledWith(['volumeId~node1', 'volumeId~node2', 'volumeId~node3', 'volumeId~node4'], 'volumeId', undefined);
223
226
  expect(cryptoService.decryptNode).toHaveBeenCalledTimes(4);
224
227
  expect(cache.setNode).toHaveBeenCalledTimes(4);
225
228
  expect(cryptoCache.setNodeKeys).toHaveBeenCalledTimes(4);
226
- expect(cache.setFolderChildrenLoaded).toHaveBeenCalledWith('parentUid');
229
+ expect(cache.setFolderChildrenLoaded).toHaveBeenCalledWith('volumeId~parentNodeid');
227
230
  });
228
231
 
229
232
  it('should remove from cache if missing on API', async () => {
230
233
  apiService.iterateChildrenNodeUids = jest.fn().mockImplementation(async function* () {
231
- yield 'node1';
232
- yield 'node2';
233
- yield 'node3';
234
+ yield node1.uid;
235
+ yield node2.uid;
236
+ yield node3.uid;
234
237
  });
235
238
  cache.getNode = jest.fn().mockImplementation((uid: string) => {
236
239
  if (uid === parentNode.uid) {
@@ -243,16 +246,16 @@ describe('nodesAccess', () => {
243
246
  yield* uids.slice(1).map((uid) => ({ uid, parentUid: parentNode.uid } as EncryptedNode));
244
247
  });
245
248
 
246
- const result = await Array.fromAsync(access.iterateFolderChildren('parentUid'));
249
+ const result = await Array.fromAsync(access.iterateFolderChildren('volumeId~parentNodeid'));
247
250
  expect(result).toMatchObject([node2, node3]);
248
- expect(cache.removeNodes).toHaveBeenCalledWith(['node1']);
251
+ expect(cache.removeNodes).toHaveBeenCalledWith([node1.uid]);
249
252
  });
250
253
 
251
254
  it('should yield all decryptable children before throwing error', async () => {
252
255
  apiService.iterateChildrenNodeUids = jest.fn().mockImplementation(async function* () {
253
- yield 'node1';
254
- yield 'node2';
255
- yield 'node3';
256
+ yield 'volumeId~node1';
257
+ yield 'volumeId~node2';
258
+ yield 'volumeId~node3';
256
259
  });
257
260
  cache.getNode = jest.fn().mockImplementation((uid: string) => {
258
261
  if (uid === parentNode.uid) {
@@ -261,7 +264,7 @@ describe('nodesAccess', () => {
261
264
  throw new Error('Entity not found');
262
265
  });
263
266
  cryptoService.decryptNode = jest.fn().mockImplementation((encryptedNode: EncryptedNode) => {
264
- if (encryptedNode.uid === 'node2') {
267
+ if (encryptedNode.uid === 'volumeId~node2') {
265
268
  throw new DecryptionError('Decryption failed');
266
269
  }
267
270
  return Promise.resolve({
@@ -270,11 +273,11 @@ describe('nodesAccess', () => {
270
273
  });
271
274
  });
272
275
 
273
- const generator = access.iterateFolderChildren('parentUid');
276
+ const generator = access.iterateFolderChildren('volumeId~parentNodeid');
274
277
  const node1 = await generator.next();
275
- expect(node1.value).toMatchObject({ uid: 'node1' });
278
+ expect(node1.value).toMatchObject({ uid: 'volumeId~node1' });
276
279
  const node2 = await generator.next();
277
- expect(node2.value).toMatchObject({ uid: 'node3' });
280
+ expect(node2.value).toMatchObject({ uid: 'volumeId~node3' });
278
281
  const node3 = generator.next();
279
282
  await expect(node3).rejects.toThrow('Failed to decrypt some nodes');
280
283
  try {
@@ -289,10 +292,10 @@ describe('nodesAccess', () => {
289
292
 
290
293
  describe('iterateTrashedNodes', () => {
291
294
  const volumeId = 'volumeId';
292
- const node1 = { uid: 'node1', isStale: false } as DecryptedNode;
293
- const node2 = { uid: 'node2', isStale: false } as DecryptedNode;
294
- const node3 = { uid: 'node3', isStale: false } as DecryptedNode;
295
- const node4 = { uid: 'node4', isStale: false } as DecryptedNode;
295
+ const node1 = { uid: 'volumeId~node1', isStale: false } as DecryptedNode;
296
+ const node2 = { uid: 'volumeId~node2', isStale: false } as DecryptedNode;
297
+ const node3 = { uid: 'volumeId~node3', isStale: false } as DecryptedNode;
298
+ const node4 = { uid: 'volumeId~node4', isStale: false } as DecryptedNode;
296
299
 
297
300
  beforeEach(() => {
298
301
  shareService.getMyFilesIDs = jest.fn().mockResolvedValue({ volumeId });
@@ -321,7 +324,7 @@ describe('nodesAccess', () => {
321
324
  const result = await Array.fromAsync(access.iterateTrashedNodes());
322
325
  expect(result).toMatchObject([node1, node2, node3, node4]);
323
326
  expect(apiService.iterateTrashedNodeUids).toHaveBeenCalledWith(volumeId, undefined);
324
- expect(apiService.iterateNodes).toHaveBeenCalledWith(['node1', 'node2', 'node3', 'node4'], volumeId, undefined);
327
+ expect(apiService.iterateNodes).toHaveBeenCalledWith(['volumeId~node1', 'volumeId~node2', 'volumeId~node3', 'volumeId~node4'], volumeId, undefined);
325
328
  expect(cryptoService.decryptNode).toHaveBeenCalledTimes(4);
326
329
  expect(cache.setNode).toHaveBeenCalledTimes(4);
327
330
  expect(cryptoCache.setNodeKeys).toHaveBeenCalledTimes(4);
@@ -333,20 +336,20 @@ describe('nodesAccess', () => {
333
336
  });
334
337
  apiService.iterateNodes = jest.fn().mockImplementation(async function* (uids: string[]) {
335
338
  // Skip first node - make it missing.
336
- yield* uids.slice(1).map((uid) => ({ uid, parentUid: 'parentUid' } as EncryptedNode));
339
+ yield* uids.slice(1).map((uid) => ({ uid, parentUid: 'volumeId~parentNodeid' } as EncryptedNode));
337
340
  });
338
341
 
339
342
  const result = await Array.fromAsync(access.iterateTrashedNodes());
340
343
  expect(result).toMatchObject([node2, node3, node4]);
341
- expect(cache.removeNodes).toHaveBeenCalledWith(['node1']);
344
+ expect(cache.removeNodes).toHaveBeenCalledWith(['volumeId~node1']);
342
345
  });
343
346
  });
344
347
 
345
348
  describe('iterateNodes', () => {
346
- const node1 = { uid: 'node1', isStale: false } as DecryptedNode;
347
- const node2 = { uid: 'node2', isStale: false } as DecryptedNode;
348
- const node3 = { uid: 'node3', isStale: false } as DecryptedNode;
349
- const node4 = { uid: 'node4', isStale: false } as DecryptedNode;
349
+ const node1 = { uid: 'volumeId~node1', isStale: false, treeEventScopeId: 'volumeId' } as DecryptedNode;
350
+ const node2 = { uid: 'volumeId~node2', isStale: false, treeEventScopeId: 'volumeId' } as DecryptedNode;
351
+ const node3 = { uid: 'volumeId~node3', isStale: false, treeEventScopeId: 'volumeId' } as DecryptedNode;
352
+ const node4 = { uid: 'volume~node4', isStale: false, treeEventScopeId: 'volumeId' } as DecryptedNode;
350
353
 
351
354
  it('should serve fully from cache', async () => {
352
355
  cache.iterateNodes = jest.fn().mockImplementation(async function* () {
@@ -356,7 +359,7 @@ describe('nodesAccess', () => {
356
359
  yield { ok: true, node: node4 };
357
360
  });
358
361
 
359
- const result = await Array.fromAsync(access.iterateNodes(['node1', 'node2', 'node3', 'node4']));
362
+ const result = await Array.fromAsync(access.iterateNodes(['volumeId~node1', 'volumeId~node2', 'volumeId~node3', 'volumeId~node4']));
360
363
  expect(result).toMatchObject([node1, node2, node3, node4]);
361
364
  expect(apiService.iterateNodes).not.toHaveBeenCalled();
362
365
  });
@@ -364,52 +367,55 @@ describe('nodesAccess', () => {
364
367
  it('should load from API', async () => {
365
368
  cache.iterateNodes = jest.fn().mockImplementation(async function* () {
366
369
  yield { ok: true, node: node1 };
367
- yield { ok: false, uid: 'node2' };
368
- yield { ok: false, uid: 'node3' };
370
+ yield { ok: false, uid: 'volumeId~node2' };
371
+ yield { ok: false, uid: 'volumeId~node3' };
369
372
  yield { ok: true, node: node4 };
370
373
  });
371
374
 
372
- const result = await Array.fromAsync(access.iterateNodes(['node1', 'node2', 'node3', 'node4']));
375
+ const result = await Array.fromAsync(access.iterateNodes(['volumeId~node1', 'volumeId~node2', 'volumeId~node3', 'volumeId~node4']));
373
376
  expect(result).toMatchObject([node1, node4, node2, node3]);
374
- expect(apiService.iterateNodes).toHaveBeenCalledWith(['node2', 'node3'], 'volumeId', undefined);
377
+ expect(apiService.iterateNodes).toHaveBeenCalledWith(['volumeId~node2', 'volumeId~node3'], 'volumeId', undefined);
375
378
  });
376
379
 
377
380
  it('should remove from cache if missing on API and return back to caller', async () => {
378
381
  cache.iterateNodes = jest.fn().mockImplementation(async function* () {
379
- yield { ok: false, uid: 'node1' };
380
- yield { ok: false, uid: 'node2' };
381
- yield { ok: false, uid: 'node3' };
382
+ yield { ok: false, uid: 'volumeId~node1' };
383
+ yield { ok: false, uid: 'volumeId~node2' };
384
+ yield { ok: false, uid: 'volumeId~node3' };
382
385
  });
383
386
  apiService.iterateNodes = jest.fn().mockImplementation(async function* (uids: string[]) {
384
387
  // Skip first node - make it missing.
385
- yield* uids.slice(1).map((uid) => ({ uid, parentUid: 'parentUid' } as EncryptedNode));
388
+ yield* uids.slice(1).map((uid) => ({ uid, parentUid: 'volumeId~parentNodeid' } as EncryptedNode));
386
389
  });
387
390
 
388
- const result = await Array.fromAsync(access.iterateNodes(['node1', 'node2', 'node3']));
389
- expect(result).toMatchObject([node2, node3, {missingUid: 'node1'}]);
390
- expect(cache.removeNodes).toHaveBeenCalledWith(['node1']);
391
+ const result = await Array.fromAsync(access.iterateNodes(['volumeId~node1', 'volumeId~node2', 'volumeId~node3']));
392
+ expect(result).toMatchObject([node2, node3, {missingUid: 'volumeId~node1'}]);
393
+ expect(cache.removeNodes).toHaveBeenCalledWith(['volumeId~node1']);
391
394
  });
392
395
 
393
396
  it('should return degraded node if parent cannot be decrypted', async () => {
394
397
  cache.iterateNodes = jest.fn().mockImplementation(async function* () {
395
- yield { ok: false, uid: 'node1' };
396
- yield { ok: false, uid: 'node2' };
397
- yield { ok: false, uid: 'node3' };
398
+ yield { ok: false, uid: 'volumeId~node1' };
399
+ yield { ok: false, uid: 'volumeId~node2' };
400
+ yield { ok: false, uid: 'volumeId~node3' };
398
401
  });
399
402
  const encryptedCrypto = {
400
403
  signatureEmail: 'signatureEmail',
401
404
  nameSignatureEmail: 'nameSignatureEmail',
402
405
  };
403
406
  apiService.iterateNodes = jest.fn().mockImplementation(async function* (uids: string[]) {
404
- yield* uids.map((uid) => ({
407
+ yield* uids.map((uid) => {
408
+ const parentUid = uid.replace('node', 'parentOfNode');
409
+ return {
405
410
  uid,
406
- parentUid: `parentUidFor:${uid}`,
411
+ parentUid,
407
412
  encryptedCrypto,
408
- } as EncryptedNode));
413
+ } as EncryptedNode
414
+ });
409
415
  });
410
416
  const decryptionError = new DecryptionError('Parent cannot be decrypted');
411
417
  jest.spyOn(access, 'getParentKeys').mockImplementation(async ({ parentUid }) => {
412
- if (parentUid === 'parentUidFor:node1') {
418
+ if (parentUid === 'volumeId~parentOfNode1') {
413
419
  throw decryptionError;
414
420
  }
415
421
  return {
@@ -417,12 +423,12 @@ describe('nodesAccess', () => {
417
423
  } as any;
418
424
  } );
419
425
 
420
- const result = await Array.fromAsync(access.iterateNodes(['node1', 'node2', 'node3']));
426
+ const result = await Array.fromAsync(access.iterateNodes(['volumeId~node1', 'volumeId~node2', 'volumeId~node3']));
421
427
  expect(result).toEqual([
422
428
  {
423
429
  ...node1,
424
430
  encryptedCrypto,
425
- parentUid: 'parentUidFor:node1',
431
+ parentUid: 'volumeId~parentOfNode1',
426
432
  name: { ok: false, error: decryptionError },
427
433
  keyAuthor: { ok: false, error: { claimedAuthor: 'signatureEmail', error: decryptionError.message } },
428
434
  nameAuthor: { ok: false, error: { claimedAuthor: 'nameSignatureEmail', error: decryptionError.message } },
@@ -457,7 +463,7 @@ describe('nodesAccess', () => {
457
463
  it('should get node parent keys', async () => {
458
464
  cryptoCache.getNodeKeys = jest.fn(() => Promise.resolve({ key: 'parentKey' } as any as DecryptedNodeKeys));
459
465
 
460
- const result = await access.getParentKeys({ shareId: undefined, parentUid: 'parentUid' });
466
+ const result = await access.getParentKeys({ shareId: undefined, parentUid: 'volumeId~parentNodeid' });
461
467
  expect(result).toEqual({ key: 'parentKey' });
462
468
  expect(shareService.getSharePrivateKey).not.toHaveBeenCalled();
463
469
  });
@@ -465,7 +471,7 @@ describe('nodesAccess', () => {
465
471
  it('should get node parent keys even if share is set', async () => {
466
472
  cryptoCache.getNodeKeys = jest.fn(() => Promise.resolve({ key: 'parentKey' } as any as DecryptedNodeKeys));
467
473
 
468
- const result = await access.getParentKeys({ shareId: 'shareId', parentUid: 'parentUid' });
474
+ const result = await access.getParentKeys({ shareId: 'shareId', parentUid: 'volume1~parentNodeid' });
469
475
  expect(result).toEqual({ key: 'parentKey' });
470
476
  expect(shareService.getSharePrivateKey).not.toHaveBeenCalled();
471
477
  });
@@ -477,7 +483,7 @@ describe('nodesAccess', () => {
477
483
  apiService.getNode = jest.fn(() => Promise.reject(new Error('API called')));
478
484
 
479
485
  try {
480
- await access.getNodeKeys('nodeId');
486
+ await access.getNodeKeys('volumeId~nodeId');
481
487
  throw new Error('Expected error');
482
488
  } catch (error: unknown) {
483
489
  expect(`${error}`).toBe('Error: API called');
@@ -490,7 +496,7 @@ describe('nodesAccess', () => {
490
496
  const nodeUid = 'nodeUid';
491
497
  const node = {
492
498
  uid: nodeUid,
493
- parentUid: 'parentUid',
499
+ parentUid: 'volume1~parentNodeid',
494
500
  encryptedName: 'encryptedName',
495
501
  } as DecryptedNode;
496
502
 
@@ -553,4 +559,39 @@ describe('nodesAccess', () => {
553
559
  expect(result).toBe('https://drive.proton.me/shareId/folder/nodeId');
554
560
  });
555
561
  });
562
+
563
+ describe('notifyNodeChanged', () => {
564
+ it('should mark node as stale', async () => {
565
+ const node = { uid: 'volumeId~nodeId', isStale: false } as DecryptedNode;
566
+ cache.getNode = jest.fn(() => Promise.resolve(node));
567
+ cache.setNode = jest.fn();
568
+ await access.notifyNodeChanged(node.uid);
569
+ expect(cache.getNode).toHaveBeenCalledWith(node.uid);
570
+ expect(cache.setNode).toHaveBeenCalledWith({...node, isStale: true});
571
+ });
572
+ it('should update parent if needed', async () => {
573
+ const node = { uid: 'volumeId~nodeId', parentUid: 'v1~pn1', isStale: false } as DecryptedNode;
574
+ cache.getNode = jest.fn(() => Promise.resolve(node));
575
+ cache.setNode = jest.fn();
576
+ await access.notifyNodeChanged(node.uid, 'v1~pn2');
577
+ expect(cache.getNode).toHaveBeenCalledWith(node.uid);
578
+ expect(cache.setNode).toHaveBeenCalledWith({...node, parentUid: 'v1~pn2', isStale: true});
579
+ });
580
+ });
581
+
582
+ describe('notifyChildCreated', () => {
583
+ it('should reset parent listing', async () => {
584
+ const nodeUid = 'VolumeId1~NodeId1';
585
+ cache.resetFolderChildrenLoaded = jest.fn();
586
+ await access.notifyChildCreated(nodeUid);
587
+ expect(cache.resetFolderChildrenLoaded).toHaveBeenCalledWith(nodeUid);
588
+ });
589
+ });
590
+
591
+ describe('notifyNodeDeleted', () => {
592
+ it('should reset parent listing', async () => {
593
+ await access.notifyNodeDeleted('v1~n1');
594
+ expect(cache.removeNodes).toHaveBeenCalledWith(['v1~n1']);
595
+ });
596
+ });
556
597
  });
@@ -28,7 +28,7 @@ const DECRYPTION_CONCURRENCY = 15;
28
28
 
29
29
  /**
30
30
  * Provides access to node metadata.
31
- *
31
+ *
32
32
  * The node access module is responsible for fetching, decrypting and caching
33
33
  * nodes metadata.
34
34
  */
@@ -140,6 +140,42 @@ export class NodesAccess {
140
140
  yield* batchLoading.loadRest();
141
141
  }
142
142
 
143
+ /**
144
+ * Call to invalidate the folder listing cache. This should be refactored into a clean
145
+ * cache layer once the cache is split off.
146
+ */
147
+ async notifyChildCreated(nodeUid: string): Promise<void> {
148
+ await this.cache.resetFolderChildrenLoaded(nodeUid);
149
+ }
150
+
151
+ /**
152
+ * Call to invalidate the node cache when a node changes. Parent can be set after a move
153
+ * to ensure parent listing of new parent is up to date if cached.
154
+ * This should be refactored into a clean cache layer once the cache is split off.
155
+ */
156
+ async notifyNodeChanged(nodeUid: string, newParentUid?: string): Promise<void> {
157
+ try {
158
+ const node = await this.cache.getNode(nodeUid);
159
+ if (node.isStale && newParentUid === null) {
160
+ return;
161
+ }
162
+ node.isStale = true;
163
+ if (newParentUid) {
164
+ node.parentUid = newParentUid;
165
+ }
166
+ await this.cache.setNode(node);
167
+ } catch (error: unknown) {
168
+ this.logger.warn(`Failed to set node ${nodeUid} as stale after sharing: ${error}`);
169
+ }
170
+ }
171
+
172
+ /**
173
+ * Call to remove a node from cache. This should be refactored when the cache is split off.
174
+ */
175
+ async notifyNodeDeleted(nodeUid: string): Promise<void> {
176
+ await this.cache.removeNodes([nodeUid]);
177
+ }
178
+
143
179
  private async loadNode(nodeUid: string): Promise<{ node: DecryptedNode, keys?: DecryptedNodeKeys }> {
144
180
  const { volumeId: ownVolumeId } = await this.shareService.getMyFilesIDs();
145
181
  const encryptedNode = await this.apiService.getNode(nodeUid, ownVolumeId);
@@ -217,6 +253,7 @@ export class NodesAccess {
217
253
  error: getErrorMessage(error),
218
254
  }),
219
255
  errors: [error],
256
+ treeEventScopeId: splitNodeUid(encryptedNode.uid).volumeId,
220
257
  },
221
258
  };
222
259
  }
@@ -272,6 +309,7 @@ export class NodesAccess {
272
309
  ...extendedAttributes,
273
310
  }),
274
311
  folder: undefined,
312
+ treeEventScopeId: splitNodeUid(unparsedNode.uid).volumeId,
275
313
  }
276
314
  }
277
315
 
@@ -286,6 +324,7 @@ export class NodesAccess {
286
324
  folder: extendedAttributes ? {
287
325
  ...extendedAttributes,
288
326
  } : undefined,
327
+ treeEventScopeId: splitNodeUid(unparsedNode.uid).volumeId,
289
328
  }
290
329
  }
291
330