@protontech/drive-sdk 0.4.1 → 0.5.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/diagnostic/sdkDiagnostic.js +1 -1
- package/dist/diagnostic/sdkDiagnostic.js.map +1 -1
- package/dist/interface/download.d.ts +4 -4
- package/dist/interface/upload.d.ts +6 -3
- package/dist/internal/apiService/apiService.d.ts +3 -0
- package/dist/internal/apiService/apiService.js +25 -2
- package/dist/internal/apiService/apiService.js.map +1 -1
- package/dist/internal/apiService/apiService.test.js +38 -0
- package/dist/internal/apiService/apiService.test.js.map +1 -1
- package/dist/internal/apiService/driveTypes.d.ts +31 -48
- package/dist/internal/apiService/errors.js +3 -0
- package/dist/internal/apiService/errors.js.map +1 -1
- package/dist/internal/apiService/errors.test.js +15 -7
- package/dist/internal/apiService/errors.test.js.map +1 -1
- package/dist/internal/asyncIteratorMap.d.ts +1 -1
- package/dist/internal/asyncIteratorMap.js +6 -1
- package/dist/internal/asyncIteratorMap.js.map +1 -1
- package/dist/internal/asyncIteratorMap.test.js +9 -0
- package/dist/internal/asyncIteratorMap.test.js.map +1 -1
- package/dist/internal/download/fileDownloader.d.ts +3 -3
- package/dist/internal/download/fileDownloader.js +5 -5
- package/dist/internal/download/fileDownloader.js.map +1 -1
- package/dist/internal/download/fileDownloader.test.js +8 -8
- package/dist/internal/download/fileDownloader.test.js.map +1 -1
- package/dist/internal/nodes/apiService.d.ts +6 -1
- package/dist/internal/nodes/apiService.js +44 -32
- package/dist/internal/nodes/apiService.js.map +1 -1
- package/dist/internal/nodes/apiService.test.js +148 -17
- package/dist/internal/nodes/apiService.test.js.map +1 -1
- package/dist/internal/nodes/debouncer.d.ts +23 -0
- package/dist/internal/nodes/debouncer.js +80 -0
- package/dist/internal/nodes/debouncer.js.map +1 -0
- package/dist/internal/nodes/debouncer.test.d.ts +1 -0
- package/dist/internal/nodes/debouncer.test.js +100 -0
- package/dist/internal/nodes/debouncer.test.js.map +1 -0
- package/dist/internal/nodes/nodesAccess.d.ts +2 -1
- package/dist/internal/nodes/nodesAccess.js +24 -5
- package/dist/internal/nodes/nodesAccess.js.map +1 -1
- package/dist/internal/nodes/nodesAccess.test.js +2 -2
- package/dist/internal/nodes/nodesAccess.test.js.map +1 -1
- package/dist/internal/photos/upload.d.ts +2 -1
- package/dist/internal/photos/upload.js +3 -3
- package/dist/internal/photos/upload.js.map +1 -1
- package/dist/internal/sharingPublic/apiService.d.ts +2 -2
- package/dist/internal/sharingPublic/apiService.js +1 -63
- package/dist/internal/sharingPublic/apiService.js.map +1 -1
- package/dist/internal/sharingPublic/cryptoCache.d.ts +0 -4
- package/dist/internal/sharingPublic/cryptoCache.js +0 -28
- package/dist/internal/sharingPublic/cryptoCache.js.map +1 -1
- package/dist/internal/sharingPublic/cryptoReporter.d.ts +16 -0
- package/dist/internal/sharingPublic/cryptoReporter.js +44 -0
- package/dist/internal/sharingPublic/cryptoReporter.js.map +1 -0
- package/dist/internal/sharingPublic/cryptoService.d.ts +3 -4
- package/dist/internal/sharingPublic/cryptoService.js +5 -43
- package/dist/internal/sharingPublic/cryptoService.js.map +1 -1
- package/dist/internal/sharingPublic/index.d.ts +21 -3
- package/dist/internal/sharingPublic/index.js +43 -12
- package/dist/internal/sharingPublic/index.js.map +1 -1
- package/dist/internal/sharingPublic/interface.d.ts +0 -1
- package/dist/internal/sharingPublic/nodes.d.ts +13 -0
- package/dist/internal/sharingPublic/nodes.js +28 -0
- package/dist/internal/sharingPublic/nodes.js.map +1 -0
- package/dist/internal/sharingPublic/session/session.d.ts +3 -3
- package/dist/internal/sharingPublic/session/url.test.js +3 -3
- package/dist/internal/sharingPublic/shares.d.ts +34 -0
- package/dist/internal/sharingPublic/shares.js +69 -0
- package/dist/internal/sharingPublic/shares.js.map +1 -0
- package/dist/internal/upload/apiService.js +10 -1
- package/dist/internal/upload/apiService.js.map +1 -1
- package/dist/internal/upload/controller.d.ts +8 -2
- package/dist/internal/upload/controller.js.map +1 -1
- package/dist/internal/upload/fileUploader.d.ts +6 -3
- package/dist/internal/upload/fileUploader.js +3 -3
- package/dist/internal/upload/fileUploader.js.map +1 -1
- package/dist/internal/upload/fileUploader.test.js +23 -11
- package/dist/internal/upload/fileUploader.test.js.map +1 -1
- package/dist/internal/upload/streamUploader.d.ts +6 -2
- package/dist/internal/upload/streamUploader.js +8 -4
- package/dist/internal/upload/streamUploader.js.map +1 -1
- package/dist/internal/upload/streamUploader.test.js +10 -6
- package/dist/internal/upload/streamUploader.test.js.map +1 -1
- package/dist/protonDriveClient.d.ts +3 -3
- package/dist/protonDriveClient.js +4 -4
- package/dist/protonDriveClient.js.map +1 -1
- package/dist/protonDrivePublicLinkClient.d.ts +31 -4
- package/dist/protonDrivePublicLinkClient.js +52 -9
- package/dist/protonDrivePublicLinkClient.js.map +1 -1
- package/package.json +1 -1
- package/src/diagnostic/sdkDiagnostic.ts +1 -1
- package/src/interface/download.ts +4 -4
- package/src/interface/upload.ts +3 -3
- package/src/internal/apiService/apiService.test.ts +50 -0
- package/src/internal/apiService/apiService.ts +33 -2
- package/src/internal/apiService/driveTypes.ts +31 -48
- package/src/internal/apiService/errors.test.ts +10 -0
- package/src/internal/apiService/errors.ts +5 -1
- package/src/internal/asyncIteratorMap.test.ts +12 -0
- package/src/internal/asyncIteratorMap.ts +8 -0
- package/src/internal/download/fileDownloader.test.ts +8 -8
- package/src/internal/download/fileDownloader.ts +5 -5
- package/src/internal/nodes/apiService.test.ts +199 -16
- package/src/internal/nodes/apiService.ts +62 -49
- package/src/internal/nodes/debouncer.test.ts +129 -0
- package/src/internal/nodes/debouncer.ts +93 -0
- package/src/internal/nodes/nodesAccess.test.ts +2 -2
- package/src/internal/nodes/nodesAccess.ts +30 -5
- package/src/internal/photos/upload.ts +4 -1
- package/src/internal/sharingPublic/apiService.ts +4 -87
- package/src/internal/sharingPublic/cryptoCache.ts +0 -34
- package/src/internal/sharingPublic/cryptoReporter.ts +73 -0
- package/src/internal/sharingPublic/cryptoService.ts +4 -80
- package/src/internal/sharingPublic/index.ts +68 -6
- package/src/internal/sharingPublic/interface.ts +0 -9
- package/src/internal/sharingPublic/nodes.ts +37 -0
- package/src/internal/sharingPublic/session/apiService.ts +1 -1
- package/src/internal/sharingPublic/session/session.ts +3 -3
- package/src/internal/sharingPublic/session/url.test.ts +3 -3
- package/src/internal/sharingPublic/shares.ts +86 -0
- package/src/internal/upload/apiService.ts +12 -1
- package/src/internal/upload/controller.ts +2 -2
- package/src/internal/upload/fileUploader.test.ts +25 -11
- package/src/internal/upload/fileUploader.ts +4 -3
- package/src/internal/upload/streamUploader.test.ts +15 -3
- package/src/internal/upload/streamUploader.ts +8 -3
- package/src/protonDriveClient.ts +4 -4
- package/src/protonDrivePublicLinkClient.ts +93 -12
- package/dist/internal/sharingPublic/manager.d.ts +0 -19
- package/dist/internal/sharingPublic/manager.js +0 -81
- package/dist/internal/sharingPublic/manager.js.map +0 -1
- package/src/internal/sharingPublic/manager.ts +0 -86
|
@@ -1,12 +1,3 @@
|
|
|
1
|
-
// TODO: use them directly, or avoid them completely
|
|
2
|
-
export type {
|
|
3
|
-
EncryptedNode,
|
|
4
|
-
EncryptedNodeFolderCrypto,
|
|
5
|
-
EncryptedNodeFileCrypto,
|
|
6
|
-
DecryptedNode,
|
|
7
|
-
DecryptedNodeKeys,
|
|
8
|
-
} from '../nodes/interface';
|
|
9
|
-
|
|
10
1
|
export interface EncryptedShareCrypto {
|
|
11
2
|
base64UrlPasswordSalt: string;
|
|
12
3
|
armoredKey: string;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { Logger } from "../../interface";
|
|
2
|
+
import { NodeAPIService } from "../nodes/apiService";
|
|
3
|
+
import { NodesCache } from "../nodes/cache";
|
|
4
|
+
import { NodesCryptoCache } from "../nodes/cryptoCache";
|
|
5
|
+
import { NodesCryptoService } from "../nodes/cryptoService";
|
|
6
|
+
import { NodesAccess } from "../nodes/nodesAccess";
|
|
7
|
+
import { isProtonDocument, isProtonSheet } from "../nodes/mediaTypes";
|
|
8
|
+
import { splitNodeUid } from "../uids";
|
|
9
|
+
import { SharingPublicSharesManager } from "./shares";
|
|
10
|
+
|
|
11
|
+
export class SharingPublicNodesAccess extends NodesAccess {
|
|
12
|
+
constructor(
|
|
13
|
+
logger: Logger,
|
|
14
|
+
apiService: NodeAPIService,
|
|
15
|
+
cache: NodesCache,
|
|
16
|
+
cryptoCache: NodesCryptoCache,
|
|
17
|
+
cryptoService: NodesCryptoService,
|
|
18
|
+
sharesService: SharingPublicSharesManager,
|
|
19
|
+
private url: string,
|
|
20
|
+
private token: string,
|
|
21
|
+
) {
|
|
22
|
+
super(logger, apiService, cache, cryptoCache, cryptoService, sharesService);
|
|
23
|
+
this.token = token;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
async getNodeUrl(nodeUid: string): Promise<string> {
|
|
27
|
+
const node = await this.getNode(nodeUid);
|
|
28
|
+
if (isProtonDocument(node.mediaType) || isProtonSheet(node.mediaType)) {
|
|
29
|
+
const { nodeId } = splitNodeUid(nodeUid);
|
|
30
|
+
const type = isProtonDocument(node.mediaType) ? 'doc' : 'sheet';
|
|
31
|
+
return `https://docs.proton.me/doc?type=${type}&mode=open-url&token=${this.token}&linkId=${nodeId}`;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// Public link doesn't support specific node URLs.
|
|
35
|
+
return this.url;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
@@ -9,7 +9,7 @@ type PostPublicLinkAuthRequest = Extract<
|
|
|
9
9
|
{ content: object }
|
|
10
10
|
>['content']['application/json'];
|
|
11
11
|
type PostPublicLinkAuthResponse =
|
|
12
|
-
|
|
12
|
+
drivePaths['/drive/urls/{token}/auth']['post']['responses']['200']['content']['application/json'];
|
|
13
13
|
|
|
14
14
|
/**
|
|
15
15
|
* Provides API communication for managing public link session (not data).
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { SRPModule } from
|
|
2
|
-
import { SharingPublicSessionAPIService } from
|
|
3
|
-
import { PublicLinkInfo, PublicLinkSrpInfo } from
|
|
1
|
+
import { SRPModule } from '../../../crypto';
|
|
2
|
+
import { SharingPublicSessionAPIService } from './apiService';
|
|
3
|
+
import { PublicLinkInfo, PublicLinkSrpInfo } from './interface';
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* Session for a public link.
|
|
@@ -9,7 +9,7 @@ describe('getTokenAndPasswordFromUrl', () => {
|
|
|
9
9
|
|
|
10
10
|
expect(result).toEqual({
|
|
11
11
|
token: 'abc123',
|
|
12
|
-
password: 'def456'
|
|
12
|
+
password: 'def456',
|
|
13
13
|
});
|
|
14
14
|
});
|
|
15
15
|
|
|
@@ -19,7 +19,7 @@ describe('getTokenAndPasswordFromUrl', () => {
|
|
|
19
19
|
|
|
20
20
|
expect(result).toEqual({
|
|
21
21
|
token: 'mytoken',
|
|
22
|
-
password: 'mypassword'
|
|
22
|
+
password: 'mypassword',
|
|
23
23
|
});
|
|
24
24
|
});
|
|
25
25
|
|
|
@@ -29,7 +29,7 @@ describe('getTokenAndPasswordFromUrl', () => {
|
|
|
29
29
|
|
|
30
30
|
expect(result).toEqual({
|
|
31
31
|
token: 'token123',
|
|
32
|
-
password: 'password456'
|
|
32
|
+
password: 'password456',
|
|
33
33
|
});
|
|
34
34
|
});
|
|
35
35
|
});
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { PrivateKey } from '../../crypto';
|
|
2
|
+
import { MetricVolumeType, ProtonDriveAccount } from '../../interface';
|
|
3
|
+
import { splitNodeUid } from '../uids';
|
|
4
|
+
import { SharingPublicAPIService } from './apiService';
|
|
5
|
+
import { SharingPublicCryptoCache } from './cryptoCache';
|
|
6
|
+
import { SharingPublicCryptoService } from './cryptoService';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Provides high-level actions for managing public link share.
|
|
10
|
+
*
|
|
11
|
+
* The public link share manager provides the same interface as the code share
|
|
12
|
+
* service so it can be used in the same way in various modules that use shares.
|
|
13
|
+
*/
|
|
14
|
+
export class SharingPublicSharesManager {
|
|
15
|
+
private promisePublicLinkRoot?: Promise<{
|
|
16
|
+
rootIds: { volumeId: string; rootNodeId: string; rootNodeUid: string };
|
|
17
|
+
shareKey: PrivateKey;
|
|
18
|
+
}>;
|
|
19
|
+
|
|
20
|
+
constructor(
|
|
21
|
+
private apiService: SharingPublicAPIService,
|
|
22
|
+
private cryptoCache: SharingPublicCryptoCache,
|
|
23
|
+
private cryptoService: SharingPublicCryptoService,
|
|
24
|
+
private account: ProtonDriveAccount,
|
|
25
|
+
private token: string,
|
|
26
|
+
) {
|
|
27
|
+
this.apiService = apiService;
|
|
28
|
+
this.cryptoCache = cryptoCache;
|
|
29
|
+
this.cryptoService = cryptoService;
|
|
30
|
+
this.account = account;
|
|
31
|
+
this.token = token;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// TODO: Rename to getRootIDs everywhere.
|
|
35
|
+
async getOwnVolumeIDs(): Promise<{ volumeId: string; rootNodeId: string; rootNodeUid: string }> {
|
|
36
|
+
const { rootIds } = await this.getPublicLinkRoot();
|
|
37
|
+
return rootIds;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
async getSharePrivateKey(): Promise<PrivateKey> {
|
|
41
|
+
const { shareKey } = await this.getPublicLinkRoot();
|
|
42
|
+
return shareKey;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
private async getPublicLinkRoot(): Promise<{
|
|
46
|
+
rootIds: { volumeId: string; rootNodeId: string; rootNodeUid: string };
|
|
47
|
+
shareKey: PrivateKey;
|
|
48
|
+
}> {
|
|
49
|
+
if (!this.promisePublicLinkRoot) {
|
|
50
|
+
this.promisePublicLinkRoot = (async () => {
|
|
51
|
+
const { encryptedNode, encryptedShare } = await this.apiService.getPublicLinkRoot(this.token);
|
|
52
|
+
|
|
53
|
+
const { volumeId, nodeId: rootNodeId } = splitNodeUid(encryptedNode.uid);
|
|
54
|
+
|
|
55
|
+
const shareKey = await this.cryptoService.decryptPublicLinkShareKey(encryptedShare);
|
|
56
|
+
await this.cryptoCache.setShareKey(shareKey);
|
|
57
|
+
|
|
58
|
+
return {
|
|
59
|
+
rootIds: { volumeId, rootNodeId, rootNodeUid: encryptedNode.uid },
|
|
60
|
+
shareKey,
|
|
61
|
+
};
|
|
62
|
+
})();
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return this.promisePublicLinkRoot;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
async getContextShareMemberEmailKey(): Promise<{
|
|
69
|
+
email: string;
|
|
70
|
+
addressId: string;
|
|
71
|
+
addressKey: PrivateKey;
|
|
72
|
+
addressKeyId: string;
|
|
73
|
+
}> {
|
|
74
|
+
const address = await this.account.getOwnPrimaryAddress();
|
|
75
|
+
return {
|
|
76
|
+
email: address.email,
|
|
77
|
+
addressId: address.addressId,
|
|
78
|
+
addressKey: address.keys[address.primaryKeyIndex].key,
|
|
79
|
+
addressKeyId: address.keys[address.primaryKeyIndex].id,
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
async getVolumeMetricContext(): Promise<MetricVolumeType> {
|
|
84
|
+
return MetricVolumeType.SharedPublic;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
@@ -110,6 +110,17 @@ export class UploadAPIService {
|
|
|
110
110
|
nodeUid: string;
|
|
111
111
|
nodeRevisionUid: string;
|
|
112
112
|
}> {
|
|
113
|
+
// The client shouldn't send the clear text size of the file.
|
|
114
|
+
// The intented upload size is needed only for early validation that
|
|
115
|
+
// the file can fit in the remaining quota to avoid data transfer when
|
|
116
|
+
// the upload would be rejected. The backend will still validate
|
|
117
|
+
// the quota during block upload and revision commit.
|
|
118
|
+
const precision = 100_000; // bytes
|
|
119
|
+
const intendedUploadSize =
|
|
120
|
+
node.intendedUploadSize && node.intendedUploadSize > precision
|
|
121
|
+
? Math.floor(node.intendedUploadSize / precision) * precision
|
|
122
|
+
: null;
|
|
123
|
+
|
|
113
124
|
const { volumeId, nodeId: parentNodeId } = splitNodeUid(parentNodeUid);
|
|
114
125
|
const result = await this.apiService.post<PostCreateDraftRequest, PostCreateDraftResponse>(
|
|
115
126
|
`drive/v2/volumes/${volumeId}/files`,
|
|
@@ -119,7 +130,7 @@ export class UploadAPIService {
|
|
|
119
130
|
Hash: node.hash,
|
|
120
131
|
MIMEType: node.mediaType,
|
|
121
132
|
ClientUID: this.clientUid || null,
|
|
122
|
-
IntendedUploadSize:
|
|
133
|
+
IntendedUploadSize: intendedUploadSize,
|
|
123
134
|
NodeKey: node.armoredNodeKey,
|
|
124
135
|
NodePassphrase: node.armoredNodePassphrase,
|
|
125
136
|
NodePassphraseSignature: node.armoredNodePassphraseSignature,
|
|
@@ -2,7 +2,7 @@ import { waitForCondition } from '../wait';
|
|
|
2
2
|
|
|
3
3
|
export class UploadController {
|
|
4
4
|
private paused = false;
|
|
5
|
-
public promise?: Promise<string>;
|
|
5
|
+
public promise?: Promise<{ nodeRevisionUid: string, nodeUid: string }>;
|
|
6
6
|
|
|
7
7
|
async waitIfPaused(): Promise<void> {
|
|
8
8
|
await waitForCondition(() => !this.paused);
|
|
@@ -16,7 +16,7 @@ export class UploadController {
|
|
|
16
16
|
this.paused = false;
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
-
async completion(): Promise<string> {
|
|
19
|
+
async completion(): Promise<{ nodeRevisionUid: string, nodeUid: string }> {
|
|
20
20
|
if (!this.promise) {
|
|
21
21
|
throw new Error('UploadController.completion() called before upload started');
|
|
22
22
|
}
|
|
@@ -108,6 +108,7 @@ describe('FileUploader', () => {
|
|
|
108
108
|
|
|
109
109
|
revisionDraft = {
|
|
110
110
|
nodeRevisionUid: 'revisionUid',
|
|
111
|
+
nodeUid: 'nodeUid',
|
|
111
112
|
nodeKeys: {
|
|
112
113
|
signatureAddress: { addressId: 'addressId' },
|
|
113
114
|
},
|
|
@@ -131,10 +132,13 @@ describe('FileUploader', () => {
|
|
|
131
132
|
abortController.signal,
|
|
132
133
|
);
|
|
133
134
|
|
|
134
|
-
startUploadSpy = jest.spyOn(uploader as any, 'startUpload').mockReturnValue(Promise.resolve(
|
|
135
|
+
startUploadSpy = jest.spyOn(uploader as any, 'startUpload').mockReturnValue(Promise.resolve({
|
|
136
|
+
nodeRevisionUid: 'revisionUid',
|
|
137
|
+
nodeUid: 'nodeUid'
|
|
138
|
+
}));
|
|
135
139
|
});
|
|
136
140
|
|
|
137
|
-
describe('
|
|
141
|
+
describe('uploadFromFile', () => {
|
|
138
142
|
// @ts-expect-error Ignore mocking File
|
|
139
143
|
const file = {
|
|
140
144
|
type: 'image/png',
|
|
@@ -146,50 +150,60 @@ describe('FileUploader', () => {
|
|
|
146
150
|
const onProgress = jest.fn();
|
|
147
151
|
|
|
148
152
|
it('should set media type if not set', async () => {
|
|
149
|
-
await uploader.
|
|
153
|
+
await uploader.uploadFromFile(file, thumbnails, onProgress);
|
|
150
154
|
|
|
151
155
|
expect(metadata.mediaType).toEqual('image/png');
|
|
152
156
|
expect(startUploadSpy).toHaveBeenCalledWith('stream', thumbnails, onProgress);
|
|
153
157
|
});
|
|
154
158
|
|
|
155
159
|
it('should set expected size if not set', async () => {
|
|
156
|
-
await uploader.
|
|
160
|
+
await uploader.uploadFromFile(file, thumbnails, onProgress);
|
|
157
161
|
|
|
158
162
|
expect(metadata.expectedSize).toEqual(file.size);
|
|
159
163
|
expect(startUploadSpy).toHaveBeenCalledWith('stream', thumbnails, onProgress);
|
|
160
164
|
});
|
|
161
165
|
|
|
162
166
|
it('should set modification time if not set', async () => {
|
|
163
|
-
await uploader.
|
|
167
|
+
await uploader.uploadFromFile(file, thumbnails, onProgress);
|
|
164
168
|
|
|
165
169
|
expect(metadata.modificationTime).toEqual(new Date(123456789));
|
|
166
170
|
expect(startUploadSpy).toHaveBeenCalledWith('stream', thumbnails, onProgress);
|
|
167
171
|
});
|
|
168
172
|
|
|
169
173
|
it('should throw an error if upload already started', async () => {
|
|
170
|
-
await uploader.
|
|
174
|
+
await uploader.uploadFromFile(file, thumbnails, onProgress);
|
|
171
175
|
|
|
172
|
-
await expect(uploader.
|
|
176
|
+
await expect(uploader.uploadFromFile(file, thumbnails, onProgress)).rejects.toThrow('Upload already started');
|
|
173
177
|
});
|
|
174
178
|
});
|
|
175
179
|
|
|
176
|
-
describe('
|
|
180
|
+
describe('uploadFromStream', () => {
|
|
177
181
|
const stream = new ReadableStream();
|
|
178
182
|
const thumbnails: Thumbnail[] = [];
|
|
179
183
|
const onProgress = jest.fn();
|
|
180
184
|
|
|
181
185
|
it('should start the upload process', async () => {
|
|
182
|
-
await uploader.
|
|
186
|
+
await uploader.uploadFromStream(stream, thumbnails, onProgress);
|
|
183
187
|
|
|
184
188
|
expect(startUploadSpy).toHaveBeenCalledWith(stream, thumbnails, onProgress);
|
|
185
189
|
});
|
|
186
190
|
|
|
187
191
|
it('should throw an error if upload already started', async () => {
|
|
188
|
-
await uploader.
|
|
192
|
+
await uploader.uploadFromStream(stream, thumbnails, onProgress);
|
|
189
193
|
|
|
190
|
-
await expect(uploader.
|
|
194
|
+
await expect(uploader.uploadFromStream(stream, thumbnails, onProgress)).rejects.toThrow(
|
|
191
195
|
'Upload already started',
|
|
192
196
|
);
|
|
193
197
|
});
|
|
198
|
+
|
|
199
|
+
it('should return correct nodeUid and nodeRevisionUid via controller completion', async () => {
|
|
200
|
+
const controller = await uploader.uploadFromStream(stream, thumbnails, onProgress);
|
|
201
|
+
const result = await controller.completion();
|
|
202
|
+
|
|
203
|
+
expect(result).toEqual({
|
|
204
|
+
nodeRevisionUid: 'revisionUid',
|
|
205
|
+
nodeUid: 'nodeUid'
|
|
206
|
+
});
|
|
207
|
+
});
|
|
194
208
|
});
|
|
195
209
|
});
|
|
@@ -46,7 +46,7 @@ class Uploader {
|
|
|
46
46
|
this.controller = new UploadController();
|
|
47
47
|
}
|
|
48
48
|
|
|
49
|
-
async
|
|
49
|
+
async uploadFromFile(
|
|
50
50
|
fileObject: File,
|
|
51
51
|
thumbnails: Thumbnail[],
|
|
52
52
|
onProgress?: (uploadedBytes: number) => void,
|
|
@@ -67,7 +67,7 @@ class Uploader {
|
|
|
67
67
|
return this.controller;
|
|
68
68
|
}
|
|
69
69
|
|
|
70
|
-
async
|
|
70
|
+
async uploadFromStream(
|
|
71
71
|
stream: ReadableStream,
|
|
72
72
|
thumbnails: Thumbnail[],
|
|
73
73
|
onProgress?: (uploadedBytes: number) => void,
|
|
@@ -83,7 +83,7 @@ class Uploader {
|
|
|
83
83
|
stream: ReadableStream,
|
|
84
84
|
thumbnails: Thumbnail[],
|
|
85
85
|
onProgress?: (uploadedBytes: number) => void,
|
|
86
|
-
): Promise<string> {
|
|
86
|
+
): Promise<{ nodeRevisionUid: string, nodeUid: string }> {
|
|
87
87
|
const uploader = await this.initStreamUploader();
|
|
88
88
|
return uploader.start(stream, thumbnails, onProgress);
|
|
89
89
|
}
|
|
@@ -119,6 +119,7 @@ class Uploader {
|
|
|
119
119
|
revisionDraft,
|
|
120
120
|
this.metadata,
|
|
121
121
|
onFinish,
|
|
122
|
+
this.controller,
|
|
122
123
|
this.signal,
|
|
123
124
|
);
|
|
124
125
|
}
|
|
@@ -108,6 +108,7 @@ describe('StreamUploader', () => {
|
|
|
108
108
|
|
|
109
109
|
revisionDraft = {
|
|
110
110
|
nodeRevisionUid: 'revisionUid',
|
|
111
|
+
nodeUid: 'nodeUid',
|
|
111
112
|
nodeKeys: {
|
|
112
113
|
signatureAddress: { addressId: 'addressId' },
|
|
113
114
|
},
|
|
@@ -131,6 +132,7 @@ describe('StreamUploader', () => {
|
|
|
131
132
|
revisionDraft,
|
|
132
133
|
metadata,
|
|
133
134
|
onFinish,
|
|
135
|
+
controller,
|
|
134
136
|
abortController.signal,
|
|
135
137
|
);
|
|
136
138
|
});
|
|
@@ -143,7 +145,12 @@ describe('StreamUploader', () => {
|
|
|
143
145
|
let stream: ReadableStream<Uint8Array>;
|
|
144
146
|
|
|
145
147
|
const verifySuccess = async () => {
|
|
146
|
-
await uploader.start(stream, thumbnails, onProgress);
|
|
148
|
+
const result = await uploader.start(stream, thumbnails, onProgress);
|
|
149
|
+
|
|
150
|
+
expect(result).toEqual({
|
|
151
|
+
nodeRevisionUid: 'revisionUid',
|
|
152
|
+
nodeUid: 'nodeUid'
|
|
153
|
+
});
|
|
147
154
|
|
|
148
155
|
const numberOfExpectedBlocks = Math.ceil(metadata.expectedSize / FILE_CHUNK_SIZE);
|
|
149
156
|
expect(uploadManager.commitDraft).toHaveBeenCalledTimes(1);
|
|
@@ -251,6 +258,8 @@ describe('StreamUploader', () => {
|
|
|
251
258
|
revisionDraft,
|
|
252
259
|
metadata,
|
|
253
260
|
onFinish,
|
|
261
|
+
controller,
|
|
262
|
+
abortController.signal,
|
|
254
263
|
);
|
|
255
264
|
|
|
256
265
|
await verifySuccess();
|
|
@@ -278,6 +287,8 @@ describe('StreamUploader', () => {
|
|
|
278
287
|
revisionDraft,
|
|
279
288
|
metadata,
|
|
280
289
|
onFinish,
|
|
290
|
+
controller,
|
|
291
|
+
abortController.signal,
|
|
281
292
|
);
|
|
282
293
|
|
|
283
294
|
await verifySuccess();
|
|
@@ -459,9 +470,10 @@ describe('StreamUploader', () => {
|
|
|
459
470
|
{
|
|
460
471
|
// Fake expected size to break verification
|
|
461
472
|
expectedSize: 1 * 1024 * 1024 + 1024,
|
|
462
|
-
|
|
463
|
-
},
|
|
473
|
+
} as UploadMetadata,
|
|
464
474
|
onFinish,
|
|
475
|
+
controller,
|
|
476
|
+
abortController.signal,
|
|
465
477
|
);
|
|
466
478
|
|
|
467
479
|
await verifyFailure(
|
|
@@ -84,6 +84,7 @@ export class StreamUploader {
|
|
|
84
84
|
protected revisionDraft: NodeRevisionDraft,
|
|
85
85
|
protected metadata: UploadMetadata,
|
|
86
86
|
protected onFinish: (failure: boolean) => Promise<void>,
|
|
87
|
+
protected uploadController: UploadController,
|
|
87
88
|
protected signal?: AbortSignal,
|
|
88
89
|
) {
|
|
89
90
|
this.telemetry = telemetry;
|
|
@@ -104,14 +105,14 @@ export class StreamUploader {
|
|
|
104
105
|
}
|
|
105
106
|
|
|
106
107
|
this.digests = new UploadDigests();
|
|
107
|
-
this.controller =
|
|
108
|
+
this.controller = uploadController;
|
|
108
109
|
}
|
|
109
110
|
|
|
110
111
|
async start(
|
|
111
112
|
stream: ReadableStream,
|
|
112
113
|
thumbnails: Thumbnail[],
|
|
113
114
|
onProgress?: (uploadedBytes: number) => void,
|
|
114
|
-
): Promise<string> {
|
|
115
|
+
): Promise<{ nodeRevisionUid: string, nodeUid: string }> {
|
|
115
116
|
let failure = false;
|
|
116
117
|
|
|
117
118
|
// File progress is tracked for telemetry - to track at what
|
|
@@ -154,7 +155,11 @@ export class StreamUploader {
|
|
|
154
155
|
await this.onFinish(failure);
|
|
155
156
|
}
|
|
156
157
|
|
|
157
|
-
return
|
|
158
|
+
return {
|
|
159
|
+
nodeRevisionUid: this.revisionDraft.nodeRevisionUid,
|
|
160
|
+
nodeUid: this.revisionDraft.nodeUid
|
|
161
|
+
}
|
|
162
|
+
|
|
158
163
|
}
|
|
159
164
|
|
|
160
165
|
private async encryptAndUploadBlocks(
|
package/src/protonDriveClient.ts
CHANGED
|
@@ -208,12 +208,12 @@ export class ProtonDriveClient {
|
|
|
208
208
|
const { httpClient, token, password } = await this.sessionManager.auth(url, customPassword);
|
|
209
209
|
return new ProtonDrivePublicLinkClient({
|
|
210
210
|
httpClient,
|
|
211
|
-
cryptoCache,
|
|
212
211
|
account,
|
|
213
212
|
openPGPCryptoModule,
|
|
214
213
|
srpModule,
|
|
215
214
|
config,
|
|
216
215
|
telemetry,
|
|
216
|
+
url,
|
|
217
217
|
token,
|
|
218
218
|
password,
|
|
219
219
|
});
|
|
@@ -726,7 +726,7 @@ export class ProtonDriveClient {
|
|
|
726
726
|
* ```typescript
|
|
727
727
|
* const downloader = await client.getFileDownloader(nodeUid, signal);
|
|
728
728
|
* const claimedSize = fileDownloader.getClaimedSizeInBytes();
|
|
729
|
-
* const downloadController = fileDownloader.
|
|
729
|
+
* const downloadController = fileDownloader.downloadToStream(stream, (downloadedBytes) => { ... });
|
|
730
730
|
*
|
|
731
731
|
* signalController.abort(); // to cancel
|
|
732
732
|
* downloadController.pause(); // to pause
|
|
@@ -786,12 +786,12 @@ export class ProtonDriveClient {
|
|
|
786
786
|
*
|
|
787
787
|
* ```typescript
|
|
788
788
|
* const uploader = await client.getFileUploader(parentFolderUid, name, metadata, signal);
|
|
789
|
-
* const uploadController = await uploader.
|
|
789
|
+
* const uploadController = await uploader.uploadFromStream(stream, thumbnails, (uploadedBytes) => { ... });
|
|
790
790
|
*
|
|
791
791
|
* signalController.abort(); // to cancel
|
|
792
792
|
* uploadController.pause(); // to pause
|
|
793
793
|
* uploadController.resume(); // to resume
|
|
794
|
-
* const nodeUid = await uploadController.completion(); // to await completion
|
|
794
|
+
* const { nodeUid, nodeRevisionUid } = await uploadController.completion(); // to await completion
|
|
795
795
|
* ```
|
|
796
796
|
*/
|
|
797
797
|
async getFileUploader(
|