@protontech/drive-sdk 0.1.2 → 0.2.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/crypto/driveCrypto.d.ts +11 -0
- package/dist/crypto/driveCrypto.js +20 -7
- package/dist/crypto/driveCrypto.js.map +1 -1
- package/dist/crypto/interface.d.ts +10 -1
- package/dist/crypto/openPGPCrypto.d.ts +18 -2
- package/dist/crypto/openPGPCrypto.js +25 -6
- package/dist/crypto/openPGPCrypto.js.map +1 -1
- package/dist/diagnostic/telemetry.d.ts +1 -1
- package/dist/diagnostic/telemetry.js +1 -1
- package/dist/diagnostic/telemetry.js.map +1 -1
- package/dist/interface/download.d.ts +46 -0
- package/dist/interface/index.d.ts +2 -2
- package/dist/interface/index.js.map +1 -1
- package/dist/interface/nodes.d.ts +26 -1
- package/dist/interface/nodes.js.map +1 -1
- package/dist/interface/telemetry.d.ts +5 -2
- package/dist/interface/telemetry.js.map +1 -1
- package/dist/internal/apiService/apiService.js +1 -1
- package/dist/internal/apiService/apiService.js.map +1 -1
- package/dist/internal/apiService/driveTypes.d.ts +78 -165
- package/dist/internal/apiService/index.d.ts +1 -1
- package/dist/internal/apiService/index.js +2 -2
- package/dist/internal/apiService/index.js.map +1 -1
- package/dist/internal/apiService/transformers.d.ts +1 -1
- package/dist/internal/apiService/transformers.js +2 -2
- package/dist/internal/apiService/transformers.js.map +1 -1
- package/dist/internal/download/blockIndex.d.ts +11 -0
- package/dist/internal/download/blockIndex.js +35 -0
- package/dist/internal/download/blockIndex.js.map +1 -0
- package/dist/internal/download/blockIndex.test.d.ts +1 -0
- package/dist/internal/download/blockIndex.test.js +147 -0
- package/dist/internal/download/blockIndex.test.js.map +1 -0
- package/dist/internal/download/fileDownloader.d.ts +6 -2
- package/dist/internal/download/fileDownloader.js +83 -6
- package/dist/internal/download/fileDownloader.js.map +1 -1
- package/dist/internal/download/fileDownloader.test.js +69 -4
- package/dist/internal/download/fileDownloader.test.js.map +1 -1
- package/dist/internal/download/interface.d.ts +4 -4
- package/dist/internal/download/seekableStream.d.ts +80 -0
- package/dist/internal/download/seekableStream.js +163 -0
- package/dist/internal/download/seekableStream.js.map +1 -0
- package/dist/internal/download/seekableStream.test.d.ts +1 -0
- package/dist/internal/download/seekableStream.test.js +149 -0
- package/dist/internal/download/seekableStream.test.js.map +1 -0
- package/dist/internal/download/telemetry.js +1 -1
- package/dist/internal/download/telemetry.js.map +1 -1
- package/dist/internal/download/telemetry.test.js +7 -7
- package/dist/internal/download/telemetry.test.js.map +1 -1
- package/dist/internal/errors.d.ts +1 -1
- package/dist/internal/errors.js +7 -1
- package/dist/internal/errors.js.map +1 -1
- package/dist/internal/errors.test.js +44 -10
- package/dist/internal/errors.test.js.map +1 -1
- package/dist/internal/events/index.js +1 -1
- package/dist/internal/events/index.js.map +1 -1
- package/dist/internal/nodes/apiService.js +16 -3
- package/dist/internal/nodes/apiService.js.map +1 -1
- package/dist/internal/nodes/apiService.test.js +43 -7
- package/dist/internal/nodes/apiService.test.js.map +1 -1
- package/dist/internal/nodes/cache.js +9 -2
- package/dist/internal/nodes/cache.js.map +1 -1
- package/dist/internal/nodes/cache.test.js +6 -1
- package/dist/internal/nodes/cache.test.js.map +1 -1
- package/dist/internal/nodes/cryptoService.d.ts +4 -1
- package/dist/internal/nodes/cryptoService.js +66 -16
- package/dist/internal/nodes/cryptoService.js.map +1 -1
- package/dist/internal/nodes/cryptoService.test.js +129 -46
- package/dist/internal/nodes/cryptoService.test.js.map +1 -1
- package/dist/internal/nodes/extendedAttributes.d.ts +2 -1
- package/dist/internal/nodes/extendedAttributes.js +27 -1
- package/dist/internal/nodes/extendedAttributes.js.map +1 -1
- package/dist/internal/nodes/extendedAttributes.test.js +59 -6
- package/dist/internal/nodes/extendedAttributes.test.js.map +1 -1
- package/dist/internal/nodes/index.test.js +1 -1
- package/dist/internal/nodes/index.test.js.map +1 -1
- package/dist/internal/nodes/interface.d.ts +18 -2
- package/dist/internal/nodes/nodesAccess.js +11 -1
- package/dist/internal/nodes/nodesAccess.js.map +1 -1
- package/dist/internal/nodes/nodesManagement.js +1 -1
- package/dist/internal/nodes/nodesManagement.js.map +1 -1
- package/dist/internal/nodes/nodesRevisions.d.ts +4 -3
- package/dist/internal/nodes/nodesRevisions.js +2 -2
- package/dist/internal/nodes/nodesRevisions.js.map +1 -1
- package/dist/internal/shares/cryptoService.js +7 -4
- package/dist/internal/shares/cryptoService.js.map +1 -1
- package/dist/internal/shares/cryptoService.test.js +5 -3
- package/dist/internal/shares/cryptoService.test.js.map +1 -1
- package/dist/internal/sharing/apiService.js +5 -5
- package/dist/internal/sharing/apiService.js.map +1 -1
- package/dist/internal/sharing/cryptoService.js +8 -5
- package/dist/internal/sharing/cryptoService.js.map +1 -1
- package/dist/internal/sharing/cryptoService.test.js +7 -4
- package/dist/internal/sharing/cryptoService.test.js.map +1 -1
- package/dist/internal/upload/telemetry.js +2 -2
- package/dist/internal/upload/telemetry.js.map +1 -1
- package/dist/internal/upload/telemetry.test.js +7 -7
- package/dist/internal/upload/telemetry.test.js.map +1 -1
- package/dist/telemetry.d.ts +2 -2
- package/dist/telemetry.js +2 -2
- package/dist/telemetry.js.map +1 -1
- package/dist/tests/telemetry.js +1 -1
- package/dist/tests/telemetry.js.map +1 -1
- package/dist/transformers.d.ts +1 -1
- package/dist/transformers.js +2 -1
- package/dist/transformers.js.map +1 -1
- package/package.json +1 -1
- package/src/crypto/driveCrypto.ts +70 -25
- package/src/crypto/interface.ts +15 -0
- package/src/crypto/openPGPCrypto.ts +37 -5
- package/src/diagnostic/telemetry.ts +1 -1
- package/src/interface/download.ts +46 -0
- package/src/interface/index.ts +2 -1
- package/src/interface/nodes.ts +28 -1
- package/src/interface/telemetry.ts +6 -1
- package/src/internal/apiService/apiService.ts +1 -1
- package/src/internal/apiService/driveTypes.ts +78 -165
- package/src/internal/apiService/index.ts +1 -1
- package/src/internal/apiService/transformers.ts +1 -1
- package/src/internal/download/blockIndex.test.ts +158 -0
- package/src/internal/download/blockIndex.ts +36 -0
- package/src/internal/download/fileDownloader.test.ts +100 -7
- package/src/internal/download/fileDownloader.ts +109 -9
- package/src/internal/download/interface.ts +4 -4
- package/src/internal/download/seekableStream.test.ts +187 -0
- package/src/internal/download/seekableStream.ts +182 -0
- package/src/internal/download/telemetry.test.ts +7 -7
- package/src/internal/download/telemetry.ts +1 -1
- package/src/internal/errors.test.ts +45 -11
- package/src/internal/errors.ts +8 -0
- package/src/internal/events/index.ts +1 -1
- package/src/internal/nodes/apiService.test.ts +59 -15
- package/src/internal/nodes/apiService.ts +21 -4
- package/src/internal/nodes/cache.test.ts +6 -1
- package/src/internal/nodes/cache.ts +9 -2
- package/src/internal/nodes/cryptoService.test.ts +139 -47
- package/src/internal/nodes/cryptoService.ts +94 -9
- package/src/internal/nodes/extendedAttributes.test.ts +60 -7
- package/src/internal/nodes/extendedAttributes.ts +37 -1
- package/src/internal/nodes/index.test.ts +1 -1
- package/src/internal/nodes/interface.ts +19 -2
- package/src/internal/nodes/nodesAccess.ts +15 -1
- package/src/internal/nodes/nodesManagement.ts +1 -1
- package/src/internal/nodes/nodesRevisions.ts +14 -5
- package/src/internal/shares/cryptoService.test.ts +5 -3
- package/src/internal/shares/cryptoService.ts +7 -4
- package/src/internal/sharing/apiService.ts +6 -6
- package/src/internal/sharing/cryptoService.test.ts +7 -4
- package/src/internal/sharing/cryptoService.ts +8 -5
- package/src/internal/upload/telemetry.test.ts +7 -7
- package/src/internal/upload/telemetry.ts +2 -2
- package/src/telemetry.ts +2 -2
- package/src/tests/telemetry.ts +1 -1
- package/src/transformers.ts +4 -2
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
interface UnderlyingSeekableSource extends UnderlyingDefaultSource<Uint8Array> {
|
|
2
|
+
seek: (position: number) => void | Promise<void>;
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* A seekable readable stream that can be used to seek to a specific position
|
|
7
|
+
* in the stream.
|
|
8
|
+
*
|
|
9
|
+
* This is useful for downloading the file in chunks or jumping to a specific
|
|
10
|
+
* position in the file when streaming a video.
|
|
11
|
+
*
|
|
12
|
+
* Example to get next chunk of data from the stream at position 100:
|
|
13
|
+
*
|
|
14
|
+
* ```
|
|
15
|
+
* const stream = new SeekableReadableStream(underlyingSource);
|
|
16
|
+
* const reader = stream.getReader();
|
|
17
|
+
* await stream.seek(100);
|
|
18
|
+
* const data = await stream.read();
|
|
19
|
+
* console.log(data);
|
|
20
|
+
* ```
|
|
21
|
+
*/
|
|
22
|
+
export class SeekableReadableStream extends ReadableStream<Uint8Array> {
|
|
23
|
+
private seekCallback: (position: number) => void | Promise<void>;
|
|
24
|
+
|
|
25
|
+
constructor({ seek, ...underlyingSource }: UnderlyingSeekableSource, queuingStrategy?: QueuingStrategy<Uint8Array>) {
|
|
26
|
+
super(underlyingSource, queuingStrategy);
|
|
27
|
+
this.seekCallback = seek;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
seek(position: number): void | Promise<void> {
|
|
31
|
+
return this.seekCallback(position);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* A buffered seekable stream that allows to seek and read specific number of
|
|
37
|
+
* bytes from the stream.
|
|
38
|
+
*
|
|
39
|
+
* This is useful for reading specific range of data from the stream. Example
|
|
40
|
+
* being video player buffering the next several bytes.
|
|
41
|
+
*
|
|
42
|
+
* The underlying source can chunk the data into various sizes. To ensure that
|
|
43
|
+
* every read operation is for the correct location, the SeekableStream is not
|
|
44
|
+
* queueing the data upfront. Instead, it will read the data and buffer it for
|
|
45
|
+
* the next read operation. If seek is called, the internal buffer is updated
|
|
46
|
+
* accordingly.
|
|
47
|
+
*
|
|
48
|
+
* Example to read 10 bytes from the stream at position 100:
|
|
49
|
+
*
|
|
50
|
+
* ```
|
|
51
|
+
* const stream = new BufferedSeekableStream(underlyingSource);
|
|
52
|
+
* await stream.seek(100);
|
|
53
|
+
* const data = await stream.read(10);
|
|
54
|
+
* console.log(data);
|
|
55
|
+
* ```
|
|
56
|
+
*/
|
|
57
|
+
export class BufferedSeekableStream extends SeekableReadableStream {
|
|
58
|
+
private buffer: Uint8Array = new Uint8Array(0);
|
|
59
|
+
private bufferPosition: number = 0;
|
|
60
|
+
private reader: ReadableStreamDefaultReader<Uint8Array> | null = null;
|
|
61
|
+
private streamClosed: boolean = false;
|
|
62
|
+
private currentPosition: number = 0;
|
|
63
|
+
|
|
64
|
+
constructor(underlyingSource: UnderlyingSeekableSource, queuingStrategy?: QueuingStrategy<Uint8Array>) {
|
|
65
|
+
// highWaterMark means that the stream will buffer up to this many
|
|
66
|
+
// bytes. We do not want to buffer anything
|
|
67
|
+
if (queuingStrategy && queuingStrategy.highWaterMark !== 0) {
|
|
68
|
+
throw new Error('highWaterMark must be 0');
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
super(underlyingSource, {
|
|
72
|
+
...queuingStrategy,
|
|
73
|
+
highWaterMark: 0,
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
this.reader = super.getReader();
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Read a specific number of bytes from the stream.
|
|
81
|
+
*
|
|
82
|
+
* When the underlying source provides more bytes than requested, the
|
|
83
|
+
* remaining bytes are buffered and used for the next read operation.
|
|
84
|
+
*
|
|
85
|
+
* @param numBytes - Number of bytes to read
|
|
86
|
+
* @returns Promise<Uint8Array> The read bytes
|
|
87
|
+
*/
|
|
88
|
+
async read(numBytes: number): Promise<{ value: Uint8Array; done: boolean }> {
|
|
89
|
+
if (numBytes <= 0) {
|
|
90
|
+
throw new Error('Invalid number of bytes to read');
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
await this.ensureBufferSize(numBytes);
|
|
94
|
+
|
|
95
|
+
const result = this.buffer.slice(this.bufferPosition, this.bufferPosition + numBytes);
|
|
96
|
+
this.bufferPosition += numBytes;
|
|
97
|
+
this.currentPosition += numBytes;
|
|
98
|
+
return {
|
|
99
|
+
value: result,
|
|
100
|
+
done: this.streamClosed,
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
private async ensureBufferSize(minBytes: number): Promise<void> {
|
|
105
|
+
const availableBytes = this.buffer.length - this.bufferPosition;
|
|
106
|
+
const neededBytes = minBytes - availableBytes;
|
|
107
|
+
|
|
108
|
+
if (neededBytes <= 0 || this.streamClosed) {
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const chunks: Uint8Array[] = [];
|
|
113
|
+
let totalBytesRead = 0;
|
|
114
|
+
|
|
115
|
+
while (totalBytesRead < neededBytes && !this.streamClosed) {
|
|
116
|
+
if (!this.reader) {
|
|
117
|
+
throw new Error('Stream reader is not available');
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const { done, value } = await this.reader.read();
|
|
121
|
+
|
|
122
|
+
if (done) {
|
|
123
|
+
this.streamClosed = true;
|
|
124
|
+
break;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
if (value) {
|
|
128
|
+
chunks.push(value);
|
|
129
|
+
totalBytesRead += value.length;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
if (chunks.length > 0) {
|
|
134
|
+
// Create new buffer with existing unused data plus new chunks
|
|
135
|
+
const unusedBufferData = this.buffer.slice(this.bufferPosition);
|
|
136
|
+
const newTotalLength = unusedBufferData.length + totalBytesRead;
|
|
137
|
+
const newBuffer = new Uint8Array(newTotalLength);
|
|
138
|
+
|
|
139
|
+
newBuffer.set(unusedBufferData, 0);
|
|
140
|
+
let offset = unusedBufferData.length;
|
|
141
|
+
for (const chunk of chunks) {
|
|
142
|
+
newBuffer.set(chunk, offset);
|
|
143
|
+
offset += chunk.length;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
this.buffer = newBuffer;
|
|
147
|
+
this.bufferPosition = 0;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Seek to the given position in the stream.
|
|
153
|
+
*
|
|
154
|
+
* If the position is outside of internally buffered data, the buffer is
|
|
155
|
+
* cleared. If the position is seeked back, the buffer is read again from
|
|
156
|
+
* the underlying source.
|
|
157
|
+
*
|
|
158
|
+
* @param position - The position to seek to in bytes.
|
|
159
|
+
*/
|
|
160
|
+
async seek(position: number): Promise<void> {
|
|
161
|
+
const endOfBufferPosition = this.currentPosition + (this.buffer.length - this.bufferPosition);
|
|
162
|
+
|
|
163
|
+
if (position > endOfBufferPosition) {
|
|
164
|
+
this.buffer = new Uint8Array(0);
|
|
165
|
+
this.bufferPosition = 0;
|
|
166
|
+
} else if (position < this.currentPosition) {
|
|
167
|
+
this.buffer = new Uint8Array(0);
|
|
168
|
+
this.bufferPosition = 0;
|
|
169
|
+
} else {
|
|
170
|
+
this.bufferPosition += position - this.currentPosition;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
await super.seek(position);
|
|
174
|
+
|
|
175
|
+
if (this.reader) {
|
|
176
|
+
this.reader.releaseLock();
|
|
177
|
+
}
|
|
178
|
+
this.reader = super.getReader();
|
|
179
|
+
this.streamClosed = false;
|
|
180
|
+
this.currentPosition = position;
|
|
181
|
+
}
|
|
182
|
+
}
|
|
@@ -14,7 +14,7 @@ describe('DownloadTelemetry', () => {
|
|
|
14
14
|
|
|
15
15
|
beforeEach(() => {
|
|
16
16
|
mockTelemetry = {
|
|
17
|
-
|
|
17
|
+
recordMetric: jest.fn(),
|
|
18
18
|
getLogger: jest.fn().mockReturnValue({
|
|
19
19
|
info: jest.fn(),
|
|
20
20
|
warn: jest.fn(),
|
|
@@ -34,7 +34,7 @@ describe('DownloadTelemetry', () => {
|
|
|
34
34
|
const error = new Error('Failed');
|
|
35
35
|
await downloadTelemetry.downloadInitFailed(nodeUid, error);
|
|
36
36
|
|
|
37
|
-
expect(mockTelemetry.
|
|
37
|
+
expect(mockTelemetry.recordMetric).toHaveBeenCalledWith({
|
|
38
38
|
eventName: 'download',
|
|
39
39
|
volumeType: 'own_volume',
|
|
40
40
|
downloadedSize: 0,
|
|
@@ -47,7 +47,7 @@ describe('DownloadTelemetry', () => {
|
|
|
47
47
|
const error = new Error('Failed');
|
|
48
48
|
await downloadTelemetry.downloadFailed(revisionUid, error, 123, 456);
|
|
49
49
|
|
|
50
|
-
expect(mockTelemetry.
|
|
50
|
+
expect(mockTelemetry.recordMetric).toHaveBeenCalledWith({
|
|
51
51
|
eventName: 'download',
|
|
52
52
|
volumeType: 'own_volume',
|
|
53
53
|
downloadedSize: 123,
|
|
@@ -60,7 +60,7 @@ describe('DownloadTelemetry', () => {
|
|
|
60
60
|
it('should log successful download (excludes error)', async () => {
|
|
61
61
|
await downloadTelemetry.downloadFinished(revisionUid, 500);
|
|
62
62
|
|
|
63
|
-
expect(mockTelemetry.
|
|
63
|
+
expect(mockTelemetry.recordMetric).toHaveBeenCalledWith({
|
|
64
64
|
eventName: 'download',
|
|
65
65
|
volumeType: 'own_volume',
|
|
66
66
|
downloadedSize: 500,
|
|
@@ -70,7 +70,7 @@ describe('DownloadTelemetry', () => {
|
|
|
70
70
|
|
|
71
71
|
describe('detect error category', () => {
|
|
72
72
|
const verifyErrorCategory = (error: string) => {
|
|
73
|
-
expect(mockTelemetry.
|
|
73
|
+
expect(mockTelemetry.recordMetric).toHaveBeenCalledWith(
|
|
74
74
|
expect.objectContaining({
|
|
75
75
|
error,
|
|
76
76
|
}),
|
|
@@ -80,7 +80,7 @@ describe('DownloadTelemetry', () => {
|
|
|
80
80
|
it('should ignore ValidationError', async () => {
|
|
81
81
|
const error = new ValidationError('Validation error');
|
|
82
82
|
await downloadTelemetry.downloadFailed(revisionUid, error, 100, 200);
|
|
83
|
-
expect(mockTelemetry.
|
|
83
|
+
expect(mockTelemetry.recordMetric).not.toHaveBeenCalled();
|
|
84
84
|
});
|
|
85
85
|
|
|
86
86
|
it('should ignore AbortError', async () => {
|
|
@@ -88,7 +88,7 @@ describe('DownloadTelemetry', () => {
|
|
|
88
88
|
error.name = 'AbortError';
|
|
89
89
|
await downloadTelemetry.downloadFailed(revisionUid, error, 100, 200);
|
|
90
90
|
|
|
91
|
-
expect(mockTelemetry.
|
|
91
|
+
expect(mockTelemetry.recordMetric).not.toHaveBeenCalled();
|
|
92
92
|
});
|
|
93
93
|
|
|
94
94
|
it('should detect "rate_limited" error for RateLimitedError', async () => {
|
|
@@ -2,20 +2,54 @@ import { VERIFICATION_STATUS } from '../crypto';
|
|
|
2
2
|
import { getVerificationMessage } from './errors';
|
|
3
3
|
|
|
4
4
|
describe('getVerificationMessage', () => {
|
|
5
|
-
const testCases: [VERIFICATION_STATUS, string | undefined, boolean, string][] = [
|
|
6
|
-
[VERIFICATION_STATUS.NOT_SIGNED, 'type', false, 'Missing signature for type'],
|
|
7
|
-
[VERIFICATION_STATUS.NOT_SIGNED, undefined, false, 'Missing signature'],
|
|
8
|
-
[VERIFICATION_STATUS.NOT_SIGNED, 'type', true, 'Missing signature for type'],
|
|
9
|
-
[VERIFICATION_STATUS.NOT_SIGNED, undefined, true, 'Missing signature'],
|
|
10
|
-
[VERIFICATION_STATUS.SIGNED_AND_INVALID, 'type', false, 'Signature verification for type failed'],
|
|
11
|
-
[VERIFICATION_STATUS.SIGNED_AND_INVALID, undefined, false, 'Signature verification failed'],
|
|
12
|
-
[
|
|
13
|
-
|
|
5
|
+
const testCases: [VERIFICATION_STATUS, Error[] | undefined, string | undefined, boolean, string][] = [
|
|
6
|
+
[VERIFICATION_STATUS.NOT_SIGNED, undefined, 'type', false, 'Missing signature for type'],
|
|
7
|
+
[VERIFICATION_STATUS.NOT_SIGNED, undefined, undefined, false, 'Missing signature'],
|
|
8
|
+
[VERIFICATION_STATUS.NOT_SIGNED, undefined, 'type', true, 'Missing signature for type'],
|
|
9
|
+
[VERIFICATION_STATUS.NOT_SIGNED, undefined, undefined, true, 'Missing signature'],
|
|
10
|
+
[VERIFICATION_STATUS.SIGNED_AND_INVALID, undefined, 'type', false, 'Signature verification for type failed'],
|
|
11
|
+
[VERIFICATION_STATUS.SIGNED_AND_INVALID, undefined, undefined, false, 'Signature verification failed'],
|
|
12
|
+
[
|
|
13
|
+
VERIFICATION_STATUS.SIGNED_AND_INVALID,
|
|
14
|
+
undefined,
|
|
15
|
+
'type',
|
|
16
|
+
true,
|
|
17
|
+
'Verification keys for type are not available',
|
|
18
|
+
],
|
|
19
|
+
[VERIFICATION_STATUS.SIGNED_AND_INVALID, undefined, undefined, true, 'Verification keys are not available'],
|
|
20
|
+
[
|
|
21
|
+
VERIFICATION_STATUS.SIGNED_AND_INVALID,
|
|
22
|
+
[new Error('error1'), new Error('error2')],
|
|
23
|
+
undefined,
|
|
24
|
+
false,
|
|
25
|
+
'Signature verification failed: error1, error2',
|
|
26
|
+
],
|
|
27
|
+
[
|
|
28
|
+
VERIFICATION_STATUS.SIGNED_AND_INVALID,
|
|
29
|
+
[new Error('error1'), new Error('error2')],
|
|
30
|
+
'type',
|
|
31
|
+
false,
|
|
32
|
+
'Signature verification for type failed: error1, error2',
|
|
33
|
+
],
|
|
34
|
+
[
|
|
35
|
+
VERIFICATION_STATUS.SIGNED_AND_INVALID,
|
|
36
|
+
[new Error('error1'), new Error('error2')],
|
|
37
|
+
undefined,
|
|
38
|
+
true,
|
|
39
|
+
'Verification keys are not available',
|
|
40
|
+
],
|
|
41
|
+
[
|
|
42
|
+
VERIFICATION_STATUS.SIGNED_AND_INVALID,
|
|
43
|
+
[new Error('error1'), new Error('error2')],
|
|
44
|
+
'type',
|
|
45
|
+
true,
|
|
46
|
+
'Verification keys for type are not available',
|
|
47
|
+
],
|
|
14
48
|
];
|
|
15
49
|
|
|
16
|
-
for (const [status, type, notAvailable, expected] of testCases) {
|
|
50
|
+
for (const [status, errors, type, notAvailable, expected] of testCases) {
|
|
17
51
|
it(`returns correct message for status ${status} with type ${type} and notAvailable ${notAvailable}`, () => {
|
|
18
|
-
expect(getVerificationMessage(status, type, notAvailable)).toBe(expected);
|
|
52
|
+
expect(getVerificationMessage(status, errors, type, notAvailable)).toBe(expected);
|
|
19
53
|
});
|
|
20
54
|
}
|
|
21
55
|
});
|
package/src/internal/errors.ts
CHANGED
|
@@ -11,6 +11,7 @@ export function getErrorMessage(error: unknown): string {
|
|
|
11
11
|
*/
|
|
12
12
|
export function getVerificationMessage(
|
|
13
13
|
verified: VERIFICATION_STATUS,
|
|
14
|
+
verificationErrors?: Error[],
|
|
14
15
|
signatureType?: string,
|
|
15
16
|
notAvailableVerificationKeys = false,
|
|
16
17
|
): string {
|
|
@@ -24,6 +25,13 @@ export function getVerificationMessage(
|
|
|
24
25
|
: c('Error').t`Verification keys are not available`;
|
|
25
26
|
}
|
|
26
27
|
|
|
28
|
+
if (verificationErrors) {
|
|
29
|
+
const errorMessage = verificationErrors?.map((e) => e.message).join(', ');
|
|
30
|
+
return signatureType
|
|
31
|
+
? c('Error').t`Signature verification for ${signatureType} failed: ${errorMessage}`
|
|
32
|
+
: c('Error').t`Signature verification failed: ${errorMessage}`;
|
|
33
|
+
}
|
|
34
|
+
|
|
27
35
|
return signatureType
|
|
28
36
|
? c('Error').t`Signature verification for ${signatureType} failed`
|
|
29
37
|
: c('Error').t`Signature verification failed`;
|
|
@@ -121,7 +121,7 @@ export class DriveEventsService {
|
|
|
121
121
|
}
|
|
122
122
|
|
|
123
123
|
private sendNumberOfVolumeSubscriptionsToTelemetry() {
|
|
124
|
-
this.telemetry.
|
|
124
|
+
this.telemetry.recordMetric({
|
|
125
125
|
eventName: 'volumeEventsSubscriptionsChanged',
|
|
126
126
|
numberOfVolumeSubscriptions: Object.keys(this.volumeEventManagers).length,
|
|
127
127
|
});
|
|
@@ -76,7 +76,7 @@ function generateAPINode() {
|
|
|
76
76
|
};
|
|
77
77
|
}
|
|
78
78
|
|
|
79
|
-
function generateFileNode(overrides = {}) {
|
|
79
|
+
function generateFileNode(overrides = {}, encryptedCryptoOverrides = {}) {
|
|
80
80
|
const node = generateNode();
|
|
81
81
|
return {
|
|
82
82
|
...node,
|
|
@@ -98,12 +98,13 @@ function generateFileNode(overrides = {}) {
|
|
|
98
98
|
armoredExtendedAttributes: '{file}',
|
|
99
99
|
thumbnails: [],
|
|
100
100
|
},
|
|
101
|
+
...encryptedCryptoOverrides,
|
|
101
102
|
},
|
|
102
103
|
...overrides,
|
|
103
104
|
};
|
|
104
105
|
}
|
|
105
106
|
|
|
106
|
-
function generateFolderNode(overrides = {}) {
|
|
107
|
+
function generateFolderNode(overrides = {}, encryptedCryptoOverrides = {}) {
|
|
107
108
|
const node = generateNode();
|
|
108
109
|
return {
|
|
109
110
|
...node,
|
|
@@ -114,6 +115,7 @@ function generateFolderNode(overrides = {}) {
|
|
|
114
115
|
armoredHashKey: 'nodeHashKey',
|
|
115
116
|
armoredExtendedAttributes: '{folder}',
|
|
116
117
|
},
|
|
118
|
+
...encryptedCryptoOverrides,
|
|
117
119
|
},
|
|
118
120
|
...overrides,
|
|
119
121
|
};
|
|
@@ -140,7 +142,8 @@ function generateNode() {
|
|
|
140
142
|
|
|
141
143
|
shareId: undefined,
|
|
142
144
|
isShared: false,
|
|
143
|
-
|
|
145
|
+
directRole: MemberRole.Admin,
|
|
146
|
+
membership: undefined,
|
|
144
147
|
|
|
145
148
|
encryptedCrypto: {
|
|
146
149
|
armoredKey: 'nodeKey',
|
|
@@ -148,6 +151,7 @@ function generateNode() {
|
|
|
148
151
|
armoredNodePassphraseSignature: 'nodePassSig',
|
|
149
152
|
nameSignatureEmail: 'nameSigEmail',
|
|
150
153
|
signatureEmail: 'sigEmail',
|
|
154
|
+
membership: undefined,
|
|
151
155
|
},
|
|
152
156
|
};
|
|
153
157
|
}
|
|
@@ -211,14 +215,34 @@ describe('nodeAPIService', () => {
|
|
|
211
215
|
},
|
|
212
216
|
Membership: {
|
|
213
217
|
Permissions: 22,
|
|
218
|
+
InviteTime: 1234567890,
|
|
219
|
+
InviterEmail: 'inviterEmail',
|
|
220
|
+
MemberSharePassphraseKeyPacket: 'memberSharePassphraseKeyPacket',
|
|
221
|
+
InviterSharePassphraseKeyPacketSignature: 'inviterSharePassphraseKeyPacketSignature',
|
|
222
|
+
InviteeSharePassphraseSessionKeySignature: 'inviteeSharePassphraseSessionKeySignature',
|
|
223
|
+
},
|
|
224
|
+
},
|
|
225
|
+
),
|
|
226
|
+
generateFolderNode(
|
|
227
|
+
{
|
|
228
|
+
isShared: true,
|
|
229
|
+
shareId: 'shareId',
|
|
230
|
+
directRole: MemberRole.Admin,
|
|
231
|
+
membership: {
|
|
232
|
+
role: MemberRole.Admin,
|
|
233
|
+
inviteTime: new Date(1234567890000),
|
|
234
|
+
},
|
|
235
|
+
},
|
|
236
|
+
{
|
|
237
|
+
membership: {
|
|
238
|
+
inviterEmail: 'inviterEmail',
|
|
239
|
+
base64MemberSharePassphraseKeyPacket: 'memberSharePassphraseKeyPacket',
|
|
240
|
+
armoredInviterSharePassphraseKeyPacketSignature: 'inviterSharePassphraseKeyPacketSignature',
|
|
241
|
+
armoredInviteeSharePassphraseSessionKeySignature:
|
|
242
|
+
'inviteeSharePassphraseSessionKeySignature',
|
|
214
243
|
},
|
|
215
244
|
},
|
|
216
245
|
),
|
|
217
|
-
generateFolderNode({
|
|
218
|
-
isShared: true,
|
|
219
|
-
shareId: 'shareId',
|
|
220
|
-
directMemberRole: MemberRole.Admin,
|
|
221
|
-
}),
|
|
222
246
|
);
|
|
223
247
|
});
|
|
224
248
|
|
|
@@ -232,14 +256,34 @@ describe('nodeAPIService', () => {
|
|
|
232
256
|
},
|
|
233
257
|
Membership: {
|
|
234
258
|
Permissions: 42,
|
|
259
|
+
InviteTime: 1234567890,
|
|
260
|
+
InviterEmail: 'inviterEmail',
|
|
261
|
+
MemberSharePassphraseKeyPacket: 'memberSharePassphraseKeyPacket',
|
|
262
|
+
InviterSharePassphraseKeyPacketSignature: 'inviterSharePassphraseKeyPacketSignature',
|
|
263
|
+
InviteeSharePassphraseSessionKeySignature: 'inviteeSharePassphraseSessionKeySignature',
|
|
264
|
+
},
|
|
265
|
+
},
|
|
266
|
+
),
|
|
267
|
+
generateFolderNode(
|
|
268
|
+
{
|
|
269
|
+
isShared: true,
|
|
270
|
+
shareId: 'shareId',
|
|
271
|
+
directRole: MemberRole.Viewer,
|
|
272
|
+
membership: {
|
|
273
|
+
role: MemberRole.Viewer,
|
|
274
|
+
inviteTime: new Date(1234567890000),
|
|
275
|
+
},
|
|
276
|
+
},
|
|
277
|
+
{
|
|
278
|
+
membership: {
|
|
279
|
+
inviterEmail: 'inviterEmail',
|
|
280
|
+
base64MemberSharePassphraseKeyPacket: 'memberSharePassphraseKeyPacket',
|
|
281
|
+
armoredInviterSharePassphraseKeyPacketSignature: 'inviterSharePassphraseKeyPacketSignature',
|
|
282
|
+
armoredInviteeSharePassphraseSessionKeySignature:
|
|
283
|
+
'inviteeSharePassphraseSessionKeySignature',
|
|
235
284
|
},
|
|
236
285
|
},
|
|
237
286
|
),
|
|
238
|
-
generateFolderNode({
|
|
239
|
-
isShared: true,
|
|
240
|
-
shareId: 'shareId',
|
|
241
|
-
directMemberRole: MemberRole.Viewer,
|
|
242
|
-
}),
|
|
243
287
|
'myVolumeId',
|
|
244
288
|
);
|
|
245
289
|
});
|
|
@@ -308,12 +352,12 @@ describe('nodeAPIService', () => {
|
|
|
308
352
|
generateFolderNode({
|
|
309
353
|
uid: 'volumeId1~nodeId1',
|
|
310
354
|
parentUid: 'volumeId1~parentNodeId1',
|
|
311
|
-
|
|
355
|
+
directRole: MemberRole.Admin,
|
|
312
356
|
}),
|
|
313
357
|
generateFolderNode({
|
|
314
358
|
uid: 'volumeId2~nodeId2',
|
|
315
359
|
parentUid: 'volumeId2~parentNodeId2',
|
|
316
|
-
|
|
360
|
+
directRole: MemberRole.Inherited,
|
|
317
361
|
}),
|
|
318
362
|
]);
|
|
319
363
|
});
|
|
@@ -8,7 +8,7 @@ import {
|
|
|
8
8
|
drivePaths,
|
|
9
9
|
isCodeOk,
|
|
10
10
|
nodeTypeNumberToNodeType,
|
|
11
|
-
|
|
11
|
+
permissionsToMemberRole,
|
|
12
12
|
} from '../apiService';
|
|
13
13
|
import { asyncIteratorRace } from '../asyncIteratorRace';
|
|
14
14
|
import { batch } from '../batch';
|
|
@@ -471,6 +471,8 @@ function linkToEncryptedNode(
|
|
|
471
471
|
link: PostLoadLinksMetadataResponse['Links'][0],
|
|
472
472
|
isAdmin: boolean,
|
|
473
473
|
): EncryptedNode {
|
|
474
|
+
const membershipRole = permissionsToMemberRole(logger, link.Membership?.Permissions);
|
|
475
|
+
|
|
474
476
|
const baseNodeMetadata = {
|
|
475
477
|
// Internal metadata
|
|
476
478
|
hash: link.Link.NameHash || undefined,
|
|
@@ -486,16 +488,31 @@ function linkToEncryptedNode(
|
|
|
486
488
|
// Sharing node metadata
|
|
487
489
|
shareId: link.Sharing?.ShareID || undefined,
|
|
488
490
|
isShared: !!link.Sharing,
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
491
|
+
directRole: isAdmin ? MemberRole.Admin : membershipRole,
|
|
492
|
+
membership: link.Membership
|
|
493
|
+
? {
|
|
494
|
+
role: membershipRole,
|
|
495
|
+
inviteTime: new Date(link.Membership.InviteTime * 1000),
|
|
496
|
+
}
|
|
497
|
+
: undefined,
|
|
492
498
|
};
|
|
499
|
+
|
|
493
500
|
const baseCryptoNodeMetadata = {
|
|
494
501
|
signatureEmail: link.Link.SignatureEmail || undefined,
|
|
495
502
|
nameSignatureEmail: link.Link.NameSignatureEmail || undefined,
|
|
496
503
|
armoredKey: link.Link.NodeKey,
|
|
497
504
|
armoredNodePassphrase: link.Link.NodePassphrase,
|
|
498
505
|
armoredNodePassphraseSignature: link.Link.NodePassphraseSignature,
|
|
506
|
+
membership: link.Membership
|
|
507
|
+
? {
|
|
508
|
+
inviterEmail: link.Membership.InviterEmail,
|
|
509
|
+
base64MemberSharePassphraseKeyPacket: link.Membership.MemberSharePassphraseKeyPacket,
|
|
510
|
+
armoredInviterSharePassphraseKeyPacketSignature:
|
|
511
|
+
link.Membership.InviterSharePassphraseKeyPacketSignature,
|
|
512
|
+
armoredInviteeSharePassphraseSessionKeySignature:
|
|
513
|
+
link.Membership.InviteeSharePassphraseSessionKeySignature,
|
|
514
|
+
}
|
|
515
|
+
: undefined,
|
|
499
516
|
};
|
|
500
517
|
|
|
501
518
|
if (link.Link.Type === 1 && link.Folder) {
|
|
@@ -12,7 +12,12 @@ function generateNode(
|
|
|
12
12
|
return {
|
|
13
13
|
uid: `${params.volumeId || 'volumeId'}~:${uid}`,
|
|
14
14
|
parentUid: `${params.volumeId || 'volumeId'}~:${parentUid}`,
|
|
15
|
-
|
|
15
|
+
directRole: MemberRole.Admin,
|
|
16
|
+
membership: {
|
|
17
|
+
role: MemberRole.Admin,
|
|
18
|
+
inviteTime: new Date(),
|
|
19
|
+
sharedBy: resultOk('test@example.com'),
|
|
20
|
+
},
|
|
16
21
|
type: NodeType.File,
|
|
17
22
|
mediaType: 'text',
|
|
18
23
|
isShared: false,
|
|
@@ -252,8 +252,9 @@ function deserialiseNode(nodeData: string): DecryptedNode {
|
|
|
252
252
|
typeof node !== 'object' ||
|
|
253
253
|
!node.uid ||
|
|
254
254
|
typeof node.uid !== 'string' ||
|
|
255
|
-
!node.
|
|
256
|
-
typeof node.
|
|
255
|
+
!node.directRole ||
|
|
256
|
+
typeof node.directRole !== 'string' ||
|
|
257
|
+
(typeof node.membership !== 'object' && node.membership !== undefined) ||
|
|
257
258
|
!node.type ||
|
|
258
259
|
typeof node.type !== 'string' ||
|
|
259
260
|
(typeof node.mediaType !== 'string' && node.mediaType !== undefined) ||
|
|
@@ -271,6 +272,12 @@ function deserialiseNode(nodeData: string): DecryptedNode {
|
|
|
271
272
|
creationTime: new Date(node.creationTime),
|
|
272
273
|
trashTime: node.trashTime ? new Date(node.trashTime) : undefined,
|
|
273
274
|
activeRevision: node.activeRevision ? deserialiseRevision(node.activeRevision) : undefined,
|
|
275
|
+
membership: node.membership
|
|
276
|
+
? {
|
|
277
|
+
...node.membership,
|
|
278
|
+
inviteTime: new Date(node.membership.inviteTime),
|
|
279
|
+
}
|
|
280
|
+
: undefined,
|
|
274
281
|
folder: node.folder
|
|
275
282
|
? {
|
|
276
283
|
...node.folder,
|