@atlaskit/media-client 20.0.0 → 20.0.2
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/CHANGELOG.md +13 -0
- package/dist/cjs/client/collection-fetcher.js +4 -44
- package/dist/cjs/client/file-fetcher/error.js +3 -19
- package/dist/cjs/client/file-fetcher/index.js +152 -167
- package/dist/cjs/client/media-client.js +3 -27
- package/dist/cjs/client/media-store/error.js +1 -17
- package/dist/cjs/client/media-store/index.js +13 -103
- package/dist/cjs/client/media-store/resolveAuth.js +2 -23
- package/dist/cjs/client/mobile-upload.js +9 -22
- package/dist/cjs/client/stargate-client.js +0 -12
- package/dist/cjs/constants.js +0 -3
- package/dist/cjs/file-streams-cache.js +0 -11
- package/dist/cjs/globalMediaEventEmitter.js +0 -3
- package/dist/cjs/identifier.js +0 -8
- package/dist/cjs/index.js +0 -38
- package/dist/cjs/models/artifacts.js +0 -4
- package/dist/cjs/models/auth-headers.js +0 -2
- package/dist/cjs/models/auth-query-parameters.js +0 -2
- package/dist/cjs/models/errors/helpers.js +0 -2
- package/dist/cjs/models/errors/index.js +4 -20
- package/dist/cjs/models/file-state.js +10 -36
- package/dist/cjs/models/media.js +2 -8
- package/dist/cjs/upload-controller.js +0 -6
- package/dist/cjs/uploader/calculateChunkSize.js +1 -10
- package/dist/cjs/uploader/error.js +3 -19
- package/dist/cjs/uploader/index.js +3 -43
- package/dist/cjs/utils/checkWebpSupport.js +3 -7
- package/dist/cjs/utils/convertBase64ToBlob.js +0 -5
- package/dist/cjs/utils/createFileDataLoader.js +4 -28
- package/dist/cjs/utils/createMediaSubject.js +0 -4
- package/dist/cjs/utils/detectEmptyFile.js +8 -15
- package/dist/cjs/utils/getDimensionsFromBlob.js +0 -14
- package/dist/cjs/utils/getImageDimensionsFromBlob.js +0 -4
- package/dist/cjs/utils/getMediaTypeFromUploadableFile.js +0 -3
- package/dist/cjs/utils/getVideoDimensionsFromBlob.js +2 -8
- package/dist/cjs/utils/hashing/hasherCreator.js +0 -20
- package/dist/cjs/utils/hashing/simpleHasher.js +0 -12
- package/dist/cjs/utils/hashing/workerHasher.js +3 -24
- package/dist/cjs/utils/imageResizeModeToFileImageMode.js +0 -2
- package/dist/cjs/utils/isImageRemote.js +0 -5
- package/dist/cjs/utils/mediaSubscribable/fromObservable.js +2 -4
- package/dist/cjs/utils/mediaSubscribable/index.js +0 -2
- package/dist/cjs/utils/mediaSubscribable/toPromise.js +1 -3
- package/dist/cjs/utils/mobileUpload/error.js +3 -19
- package/dist/cjs/utils/mobileUpload/helpers.js +2 -26
- package/dist/cjs/utils/mobileUpload/index.js +0 -4
- package/dist/cjs/utils/mobileUpload/servicesCache.js +0 -2
- package/dist/cjs/utils/mobileUpload/stateMachine/index.js +0 -22
- package/dist/cjs/utils/mobileUpload/stateMachine/states/processing.js +0 -2
- package/dist/cjs/utils/mobileUpload/stateMachine/states/uploading.js +0 -7
- package/dist/cjs/utils/overrideMediaTypeIfUnknown.js +0 -4
- package/dist/cjs/utils/polling/errors.js +1 -17
- package/dist/cjs/utils/polling/index.js +1 -30
- package/dist/cjs/utils/request/errors.js +8 -24
- package/dist/cjs/utils/request/helpers.js +19 -103
- package/dist/cjs/utils/request/index.js +15 -26
- package/dist/cjs/utils/safeUnsubscribe.js +0 -2
- package/dist/cjs/utils/setTimeoutPromise.js +0 -4
- package/dist/cjs/utils/shouldFetchRemoteFileStates.js +0 -21
- package/dist/cjs/utils/url.js +4 -27
- package/dist/cjs/utils/with-media-client-hoc.js +12 -28
- package/dist/cjs/version.json +1 -1
- package/dist/es2019/client/collection-fetcher.js +6 -18
- package/dist/es2019/client/file-fetcher/error.js +0 -2
- package/dist/es2019/client/file-fetcher/index.js +138 -70
- package/dist/es2019/client/media-client.js +4 -14
- package/dist/es2019/client/media-store/error.js +0 -2
- package/dist/es2019/client/media-store/index.js +37 -52
- package/dist/es2019/client/media-store/resolveAuth.js +1 -5
- package/dist/es2019/client/mobile-upload.js +0 -8
- package/dist/es2019/client/stargate-client.js +0 -3
- package/dist/es2019/constants.js +0 -1
- package/dist/es2019/file-streams-cache.js +0 -11
- package/dist/es2019/globalMediaEventEmitter.js +0 -4
- package/dist/es2019/index.js +4 -11
- package/dist/es2019/models/artifacts.js +0 -2
- package/dist/es2019/models/errors/index.js +5 -4
- package/dist/es2019/models/file-state.js +6 -7
- package/dist/es2019/models/media.js +2 -3
- package/dist/es2019/upload-controller.js +0 -3
- package/dist/es2019/uploader/calculateChunkSize.js +1 -4
- package/dist/es2019/uploader/error.js +0 -2
- package/dist/es2019/uploader/index.js +0 -13
- package/dist/es2019/utils/checkWebpSupport.js +3 -4
- package/dist/es2019/utils/convertBase64ToBlob.js +0 -2
- package/dist/es2019/utils/createFileDataLoader.js +6 -9
- package/dist/es2019/utils/createMediaSubject.js +0 -2
- package/dist/es2019/utils/detectEmptyFile.js +1 -5
- package/dist/es2019/utils/getDimensionsFromBlob.js +0 -3
- package/dist/es2019/utils/getImageDimensionsFromBlob.js +0 -2
- package/dist/es2019/utils/getVideoDimensionsFromBlob.js +2 -1
- package/dist/es2019/utils/hashing/hasherCreator.js +0 -2
- package/dist/es2019/utils/hashing/simpleHasher.js +0 -3
- package/dist/es2019/utils/hashing/workerHasher.js +1 -16
- package/dist/es2019/utils/isImageRemote.js +0 -2
- package/dist/es2019/utils/mediaSubscribable/fromObservable.js +2 -1
- package/dist/es2019/utils/mediaSubscribable/toPromise.js +1 -1
- package/dist/es2019/utils/mobileUpload/error.js +0 -2
- package/dist/es2019/utils/mobileUpload/helpers.js +2 -7
- package/dist/es2019/utils/mobileUpload/stateMachine/index.js +0 -2
- package/dist/es2019/utils/mobileUpload/stateMachine/states/uploading.js +2 -1
- package/dist/es2019/utils/overrideMediaTypeIfUnknown.js +0 -1
- package/dist/es2019/utils/polling/errors.js +0 -2
- package/dist/es2019/utils/polling/index.js +3 -20
- package/dist/es2019/utils/request/errors.js +0 -2
- package/dist/es2019/utils/request/helpers.js +24 -35
- package/dist/es2019/utils/request/index.js +2 -2
- package/dist/es2019/utils/shouldFetchRemoteFileStates.js +1 -5
- package/dist/es2019/utils/url.js +6 -14
- package/dist/es2019/utils/with-media-client-hoc.js +10 -7
- package/dist/es2019/version.json +1 -1
- package/dist/esm/client/collection-fetcher.js +4 -34
- package/dist/esm/client/file-fetcher/error.js +3 -12
- package/dist/esm/client/file-fetcher/index.js +154 -137
- package/dist/esm/client/media-client.js +4 -17
- package/dist/esm/client/media-store/error.js +1 -10
- package/dist/esm/client/media-store/index.js +13 -89
- package/dist/esm/client/media-store/resolveAuth.js +2 -13
- package/dist/esm/client/mobile-upload.js +9 -15
- package/dist/esm/client/stargate-client.js +0 -7
- package/dist/esm/constants.js +0 -1
- package/dist/esm/file-streams-cache.js +0 -6
- package/dist/esm/globalMediaEventEmitter.js +0 -1
- package/dist/esm/index.js +4 -11
- package/dist/esm/models/artifacts.js +0 -2
- package/dist/esm/models/errors/index.js +5 -12
- package/dist/esm/models/file-state.js +10 -17
- package/dist/esm/models/media.js +2 -3
- package/dist/esm/upload-controller.js +0 -2
- package/dist/esm/uploader/calculateChunkSize.js +1 -4
- package/dist/esm/uploader/error.js +3 -12
- package/dist/esm/uploader/index.js +3 -32
- package/dist/esm/utils/checkWebpSupport.js +3 -4
- package/dist/esm/utils/convertBase64ToBlob.js +0 -3
- package/dist/esm/utils/createFileDataLoader.js +4 -18
- package/dist/esm/utils/createMediaSubject.js +0 -2
- package/dist/esm/utils/detectEmptyFile.js +8 -12
- package/dist/esm/utils/getDimensionsFromBlob.js +0 -7
- package/dist/esm/utils/getImageDimensionsFromBlob.js +0 -2
- package/dist/esm/utils/getVideoDimensionsFromBlob.js +2 -3
- package/dist/esm/utils/hashing/hasherCreator.js +0 -9
- package/dist/esm/utils/hashing/simpleHasher.js +0 -4
- package/dist/esm/utils/hashing/workerHasher.js +3 -18
- package/dist/esm/utils/isImageRemote.js +0 -3
- package/dist/esm/utils/mediaSubscribable/fromObservable.js +2 -1
- package/dist/esm/utils/mediaSubscribable/toPromise.js +1 -1
- package/dist/esm/utils/mobileUpload/error.js +3 -12
- package/dist/esm/utils/mobileUpload/helpers.js +2 -11
- package/dist/esm/utils/mobileUpload/stateMachine/index.js +0 -6
- package/dist/esm/utils/mobileUpload/stateMachine/states/uploading.js +0 -3
- package/dist/esm/utils/overrideMediaTypeIfUnknown.js +0 -1
- package/dist/esm/utils/polling/errors.js +1 -10
- package/dist/esm/utils/polling/index.js +1 -27
- package/dist/esm/utils/request/errors.js +8 -17
- package/dist/esm/utils/request/helpers.js +22 -76
- package/dist/esm/utils/request/index.js +15 -20
- package/dist/esm/utils/shouldFetchRemoteFileStates.js +1 -15
- package/dist/esm/utils/url.js +4 -18
- package/dist/esm/utils/with-media-client-hoc.js +12 -17
- package/dist/esm/version.json +1 -1
- package/dist/types/client/file-fetcher/index.d.ts +6 -2
- package/package.json +8 -6
- package/report.api.md +4 -1
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { DATA_UNIT } from '../models/media';
|
|
2
2
|
import { MAX_UPLOAD_FILE_SIZE } from '../constants';
|
|
3
3
|
export const fileSizeError = 'fileSizeExceedsLimit';
|
|
4
|
+
|
|
4
5
|
/**
|
|
5
6
|
* This is a helper to dynamically calculate the chunk size for a given file size.
|
|
6
7
|
*
|
|
@@ -15,18 +16,14 @@ export const calculateChunkSize = fileSize => {
|
|
|
15
16
|
if (fileSize > MAX_UPLOAD_FILE_SIZE) {
|
|
16
17
|
throw new Error(fileSizeError);
|
|
17
18
|
}
|
|
18
|
-
|
|
19
19
|
if (fileSize <= 5 * DATA_UNIT.GB) {
|
|
20
20
|
return 5 * DATA_UNIT.MB;
|
|
21
21
|
}
|
|
22
|
-
|
|
23
22
|
if (fileSize > 5 * DATA_UNIT.GB && fileSize <= 50 * DATA_UNIT.GB) {
|
|
24
23
|
return 50 * DATA_UNIT.MB;
|
|
25
24
|
}
|
|
26
|
-
|
|
27
25
|
if (fileSize > 50 * DATA_UNIT.GB && fileSize <= 0.95 * DATA_UNIT.TB) {
|
|
28
26
|
return 100 * DATA_UNIT.MB;
|
|
29
27
|
}
|
|
30
|
-
|
|
31
28
|
return 210 * DATA_UNIT.MB;
|
|
32
29
|
};
|
|
@@ -6,7 +6,6 @@ export class UploaderError extends BaseMediaClientError {
|
|
|
6
6
|
this.id = id;
|
|
7
7
|
this.metadata = metadata;
|
|
8
8
|
}
|
|
9
|
-
|
|
10
9
|
get attributes() {
|
|
11
10
|
const {
|
|
12
11
|
reason,
|
|
@@ -23,7 +22,6 @@ export class UploaderError extends BaseMediaClientError {
|
|
|
23
22
|
occurrenceKey
|
|
24
23
|
};
|
|
25
24
|
}
|
|
26
|
-
|
|
27
25
|
}
|
|
28
26
|
export function isUploaderError(err) {
|
|
29
27
|
return err instanceof UploaderError;
|
|
@@ -5,22 +5,18 @@ import { createHasher } from '../utils/hashing/hasherCreator';
|
|
|
5
5
|
import { UploaderError } from './error';
|
|
6
6
|
import { CHUNK_SIZE, PROCESSING_BATCH_SIZE } from '../constants';
|
|
7
7
|
import { calculateChunkSize, fileSizeError } from './calculateChunkSize';
|
|
8
|
-
|
|
9
8
|
const hashingFunction = async blob => {
|
|
10
9
|
const hasher = await createHasher();
|
|
11
10
|
return hasher.hash(blob);
|
|
12
11
|
};
|
|
13
|
-
|
|
14
12
|
const createProbingFunction = (store, deferredUploadId, collectionName, traceContext) => async chunks => {
|
|
15
13
|
const response = await store.probeChunks(hashedChunks(chunks), await deferredUploadId, collectionName, traceContext);
|
|
16
14
|
const results = response.data.results;
|
|
17
15
|
return Object.values(results).map(result => result.exists);
|
|
18
16
|
};
|
|
19
|
-
|
|
20
17
|
const createUploadingFunction = (store, deferredUploadId, collectionName, traceContext) => async chunk => {
|
|
21
18
|
return await store.uploadChunk(chunk.hash, chunk.blob, await deferredUploadId, chunk.partNumber, collectionName, traceContext);
|
|
22
19
|
};
|
|
23
|
-
|
|
24
20
|
const createProcessingFunction = (store, deferredUploadId, collection, traceContext) => {
|
|
25
21
|
let offset = 0;
|
|
26
22
|
return async chunks => {
|
|
@@ -31,7 +27,6 @@ const createProcessingFunction = (store, deferredUploadId, collection, traceCont
|
|
|
31
27
|
offset += chunks.length;
|
|
32
28
|
};
|
|
33
29
|
};
|
|
34
|
-
|
|
35
30
|
const createFileFromUpload = async (file, store, uploadableFileUpfrontIds, uploadId, traceContext) => {
|
|
36
31
|
const {
|
|
37
32
|
collection,
|
|
@@ -52,7 +47,6 @@ const createFileFromUpload = async (file, store, uploadableFileUpfrontIds, uploa
|
|
|
52
47
|
replaceFileId: id
|
|
53
48
|
}, traceContext);
|
|
54
49
|
};
|
|
55
|
-
|
|
56
50
|
export const uploadFile = (file, store, uploadableFileUpfrontIds, callbacks, traceContext) => {
|
|
57
51
|
const {
|
|
58
52
|
content,
|
|
@@ -64,7 +58,6 @@ export const uploadFile = (file, store, uploadableFileUpfrontIds, callbacks, tra
|
|
|
64
58
|
occurrenceKey
|
|
65
59
|
} = uploadableFileUpfrontIds;
|
|
66
60
|
let chunkSize = CHUNK_SIZE;
|
|
67
|
-
|
|
68
61
|
try {
|
|
69
62
|
if (content instanceof Blob) {
|
|
70
63
|
chunkSize = calculateChunkSize(content.size);
|
|
@@ -76,14 +69,12 @@ export const uploadFile = (file, store, uploadableFileUpfrontIds, callbacks, tra
|
|
|
76
69
|
occurrenceKey: occurrenceKey
|
|
77
70
|
}));
|
|
78
71
|
}
|
|
79
|
-
|
|
80
72
|
return {
|
|
81
73
|
cancel: () => {
|
|
82
74
|
callbacks === null || callbacks === void 0 ? void 0 : callbacks.onUploadFinish('canceled');
|
|
83
75
|
}
|
|
84
76
|
};
|
|
85
77
|
}
|
|
86
|
-
|
|
87
78
|
const chunkinatorObservable = chunkinator(content, {
|
|
88
79
|
hashingFunction,
|
|
89
80
|
hashingConcurrency: 5,
|
|
@@ -100,11 +91,8 @@ export const uploadFile = (file, store, uploadableFileUpfrontIds, callbacks, tra
|
|
|
100
91
|
callbacks.onProgress(progress);
|
|
101
92
|
}
|
|
102
93
|
}
|
|
103
|
-
|
|
104
94
|
});
|
|
105
|
-
|
|
106
95
|
const onUploadFinish = callbacks && callbacks.onUploadFinish || (() => {});
|
|
107
|
-
|
|
108
96
|
const subscription = from(deferredUploadId).pipe(concatMap(uploadId => chunkinatorObservable.pipe(concatMap(() => from(createFileFromUpload(file, store, uploadableFileUpfrontIds, uploadId, traceContext)))))).subscribe({
|
|
109
97
|
error: err => onUploadFinish(err),
|
|
110
98
|
complete: () => onUploadFinish()
|
|
@@ -116,5 +104,4 @@ export const uploadFile = (file, store, uploadableFileUpfrontIds, callbacks, tra
|
|
|
116
104
|
}
|
|
117
105
|
};
|
|
118
106
|
};
|
|
119
|
-
|
|
120
107
|
const hashedChunks = chunks => chunks.map(chunk => chunk.hash);
|
|
@@ -8,14 +8,13 @@ export const checkWebpSupport = () => {
|
|
|
8
8
|
if (isSupported !== undefined) {
|
|
9
9
|
return Promise.resolve(isSupported);
|
|
10
10
|
}
|
|
11
|
-
|
|
12
11
|
return new Promise(resolve => {
|
|
13
|
-
const img = new Image();
|
|
12
|
+
const img = new Image();
|
|
13
|
+
|
|
14
|
+
// Following base64 encoded binary content is in webp format. If browser supports this standard,
|
|
14
15
|
// 2px height image will be displayed. If not, standard "not found" image placeholder will be
|
|
15
16
|
// displayed and it will be not 2px height.
|
|
16
|
-
|
|
17
17
|
img.src = 'data:image/webp;base64,UklGRi4AAABXRUJQVlA4TCEAAAAvAUAAEB8wAiMwAgSSNtse/cXjxyCCmrYNWPwmHRH9jwMA';
|
|
18
|
-
|
|
19
18
|
img.onload = img.onerror = () => {
|
|
20
19
|
isSupported = img.height === 2;
|
|
21
20
|
resolve(isSupported);
|
|
@@ -3,14 +3,12 @@ export const convertBase64ToBlob = base64 => {
|
|
|
3
3
|
const base64Data = base64.split(',')[1];
|
|
4
4
|
const byteCharacters = atob(base64Data);
|
|
5
5
|
const byteArrays = [];
|
|
6
|
-
|
|
7
6
|
for (let offset = 0; offset < byteCharacters.length; offset += sliceSize) {
|
|
8
7
|
const slice = byteCharacters.slice(offset, offset + sliceSize);
|
|
9
8
|
const byteNumbers = slice.split('').map((_, i) => slice.charCodeAt(i));
|
|
10
9
|
const byteArray = new Uint8Array(byteNumbers);
|
|
11
10
|
byteArrays.push(byteArray);
|
|
12
11
|
}
|
|
13
|
-
|
|
14
12
|
return new Blob(byteArrays, {
|
|
15
13
|
type: 'image/jpeg'
|
|
16
14
|
});
|
|
@@ -4,13 +4,10 @@ import 'setimmediate';
|
|
|
4
4
|
import Dataloader from 'dataloader';
|
|
5
5
|
import { getRandomHex } from '@atlaskit/media-common';
|
|
6
6
|
export const MAX_BATCH_SIZE = 100;
|
|
7
|
-
|
|
8
7
|
const isBatchLoadingErrorResult = result => {
|
|
9
8
|
return result.error instanceof Error;
|
|
10
9
|
};
|
|
11
|
-
|
|
12
10
|
const makeCacheKey = (id, collection) => collection ? `${id}-${collection}` : id;
|
|
13
|
-
|
|
14
11
|
export const getItemsFromKeys = (dataloaderKeys, fileItems) => {
|
|
15
12
|
const itemsByKey = fileItems.reduce((prev, fileItem) => {
|
|
16
13
|
const {
|
|
@@ -18,7 +15,8 @@ export const getItemsFromKeys = (dataloaderKeys, fileItems) => {
|
|
|
18
15
|
collection
|
|
19
16
|
} = fileItem;
|
|
20
17
|
const key = makeCacheKey(id, collection);
|
|
21
|
-
prev[key] = isBatchLoadingErrorResult(fileItem) ? fileItem.error : {
|
|
18
|
+
prev[key] = isBatchLoadingErrorResult(fileItem) ? fileItem.error : {
|
|
19
|
+
...fileItem.details,
|
|
22
20
|
metadataTraceContext: fileItem.metadataTraceContext
|
|
23
21
|
};
|
|
24
22
|
return prev;
|
|
@@ -32,7 +30,6 @@ export const getItemsFromKeys = (dataloaderKeys, fileItems) => {
|
|
|
32
30
|
return itemsByKey[key] || null;
|
|
33
31
|
});
|
|
34
32
|
};
|
|
35
|
-
|
|
36
33
|
/**
|
|
37
34
|
* Returns a function that, given Array<DataloaderKey>, resolves to an array of same length containing either DataloaderResult or Error.
|
|
38
35
|
* Such contract is formalised by Dataloader 1.0, @see https://github.com/graphql/dataloader
|
|
@@ -47,12 +44,12 @@ export function createBatchLoadingFunc(mediaStore) {
|
|
|
47
44
|
const nonCollectionName = '__media-single-file-collection__';
|
|
48
45
|
const fileIdsByCollection = keys.reduce((acc, key) => {
|
|
49
46
|
const collectionName = key.collectionName || nonCollectionName;
|
|
50
|
-
const fileIds = acc[collectionName] || [];
|
|
47
|
+
const fileIds = acc[collectionName] || [];
|
|
51
48
|
|
|
49
|
+
// de-duplicate ids in collection
|
|
52
50
|
if (fileIds.indexOf(key.id) === -1) {
|
|
53
51
|
fileIds.push(key.id);
|
|
54
52
|
}
|
|
55
|
-
|
|
56
53
|
acc[collectionName] = fileIds;
|
|
57
54
|
return acc;
|
|
58
55
|
}, {});
|
|
@@ -64,10 +61,10 @@ export function createBatchLoadingFunc(mediaStore) {
|
|
|
64
61
|
};
|
|
65
62
|
const fileIds = fileIdsByCollection[collectionNameKey];
|
|
66
63
|
const collectionName = collectionNameKey === nonCollectionName ? undefined : collectionNameKey;
|
|
67
|
-
|
|
68
64
|
try {
|
|
69
65
|
const response = await mediaStore.getItems(fileIds, collectionName, metadataTraceContext);
|
|
70
|
-
const itemsWithMetadataTraceContext = response.data.items.map(item => ({
|
|
66
|
+
const itemsWithMetadataTraceContext = response.data.items.map(item => ({
|
|
67
|
+
...item,
|
|
71
68
|
metadataTraceContext
|
|
72
69
|
}));
|
|
73
70
|
items.push(...itemsWithMetadataTraceContext);
|
|
@@ -1,12 +1,10 @@
|
|
|
1
1
|
import { ReplaySubject } from 'rxjs/ReplaySubject';
|
|
2
2
|
export function createMediaSubject(initialState) {
|
|
3
3
|
const subject = new ReplaySubject(1);
|
|
4
|
-
|
|
5
4
|
if (initialState instanceof Error) {
|
|
6
5
|
subject.error(initialState);
|
|
7
6
|
} else if (initialState) {
|
|
8
7
|
subject.next(initialState);
|
|
9
8
|
}
|
|
10
|
-
|
|
11
9
|
return subject;
|
|
12
10
|
}
|
|
@@ -11,8 +11,7 @@
|
|
|
11
11
|
*
|
|
12
12
|
* Being encapsulated in this function, we can always improve the detection transparently.
|
|
13
13
|
*/
|
|
14
|
-
export const EMPTY_FILE_HOURS_ELAPSED_TOLERANCE_MS = 12 * 1000 * 60 * 60;
|
|
15
|
-
/* 12 hours */
|
|
14
|
+
export const EMPTY_FILE_HOURS_ELAPSED_TOLERANCE_MS = 12 * 1000 * 60 * 60; /* 12 hours */
|
|
16
15
|
|
|
17
16
|
export function isEmptyFile(fileDetails, now = Date.now()) {
|
|
18
17
|
const {
|
|
@@ -25,14 +24,11 @@ export function isEmptyFile(fileDetails, now = Date.now()) {
|
|
|
25
24
|
size,
|
|
26
25
|
createdAt
|
|
27
26
|
} = fileDetails;
|
|
28
|
-
|
|
29
27
|
if (!artifacts && !mediaType && !mimeType && !name && !processingStatus && !representations && !size && typeof createdAt === 'number') {
|
|
30
28
|
const msSinceFileCreation = now - createdAt;
|
|
31
|
-
|
|
32
29
|
if (msSinceFileCreation > EMPTY_FILE_HOURS_ELAPSED_TOLERANCE_MS) {
|
|
33
30
|
return true;
|
|
34
31
|
}
|
|
35
32
|
}
|
|
36
|
-
|
|
37
33
|
return false;
|
|
38
34
|
}
|
|
@@ -5,17 +5,14 @@ export const getDimensionsFromBlob = async (mediaType, blob) => {
|
|
|
5
5
|
case 'image':
|
|
6
6
|
{
|
|
7
7
|
const url = URL.createObjectURL(blob);
|
|
8
|
-
|
|
9
8
|
try {
|
|
10
9
|
return await getImageDimensionsFromBlob(url);
|
|
11
10
|
} finally {
|
|
12
11
|
URL.revokeObjectURL(url);
|
|
13
12
|
}
|
|
14
13
|
}
|
|
15
|
-
|
|
16
14
|
case 'video':
|
|
17
15
|
return getVideoDimensionsFromBlob(blob);
|
|
18
|
-
|
|
19
16
|
default:
|
|
20
17
|
throw new Error(`Can't extract dimensions from ${mediaType}`);
|
|
21
18
|
}
|
|
@@ -4,8 +4,9 @@ export const getVideoDimensionsFromBlob = async blob => {
|
|
|
4
4
|
const video = document.createElement('video');
|
|
5
5
|
video.preload = 'metadata';
|
|
6
6
|
video.src = url;
|
|
7
|
-
video.muted = true;
|
|
7
|
+
video.muted = true;
|
|
8
8
|
|
|
9
|
+
// loadedmetadata, loadeddata, play, playing
|
|
9
10
|
video.addEventListener('loadedmetadata', function timeupdateHandler() {
|
|
10
11
|
video.removeEventListener('loadedmetadata', timeupdateHandler);
|
|
11
12
|
resolve({
|
|
@@ -2,7 +2,6 @@ let hasher = null;
|
|
|
2
2
|
export const destroyHasher = () => hasher = null;
|
|
3
3
|
export const createHasher = async () => {
|
|
4
4
|
const numWorkers = 3;
|
|
5
|
-
|
|
6
5
|
if (!hasher) {
|
|
7
6
|
try {
|
|
8
7
|
const {
|
|
@@ -16,6 +15,5 @@ export const createHasher = async () => {
|
|
|
16
15
|
hasher = new SimpleHasher();
|
|
17
16
|
}
|
|
18
17
|
}
|
|
19
|
-
|
|
20
18
|
return hasher;
|
|
21
19
|
};
|
|
@@ -4,13 +4,10 @@ export class SimpleHasher {
|
|
|
4
4
|
return new Promise((resolve, reject) => {
|
|
5
5
|
const reader = new FileReader();
|
|
6
6
|
reader.readAsArrayBuffer(blob);
|
|
7
|
-
|
|
8
7
|
reader.onload = () => {
|
|
9
8
|
resolve(Rusha.createHash().update(reader.result).digest('hex'));
|
|
10
9
|
};
|
|
11
|
-
|
|
12
10
|
reader.onerror = reject;
|
|
13
11
|
});
|
|
14
12
|
}
|
|
15
|
-
|
|
16
13
|
}
|
|
@@ -4,18 +4,14 @@ import Rusha from 'rusha';
|
|
|
4
4
|
export class WorkerHasher {
|
|
5
5
|
constructor(numOfWorkers) {
|
|
6
6
|
_defineProperty(this, "workers", []);
|
|
7
|
-
|
|
8
7
|
_defineProperty(this, "jobs", {});
|
|
9
|
-
|
|
10
8
|
for (let i = 0; i < numOfWorkers; ++i) {
|
|
11
9
|
this.workers.push(this.createWorker());
|
|
12
10
|
}
|
|
13
11
|
}
|
|
14
|
-
|
|
15
12
|
hash(chunk) {
|
|
16
13
|
return this.calculateHashInWorker(chunk);
|
|
17
14
|
}
|
|
18
|
-
|
|
19
15
|
createWorker() {
|
|
20
16
|
const worker = Rusha.createWorker();
|
|
21
17
|
const hasherWorker = {
|
|
@@ -27,10 +23,8 @@ export class WorkerHasher {
|
|
|
27
23
|
});
|
|
28
24
|
return hasherWorker;
|
|
29
25
|
}
|
|
30
|
-
|
|
31
26
|
handleWorkerMessage(event, hasherWorker) {
|
|
32
27
|
const id = event.data.id;
|
|
33
|
-
|
|
34
28
|
if (this.jobs[id]) {
|
|
35
29
|
const {
|
|
36
30
|
resolve,
|
|
@@ -38,7 +32,6 @@ export class WorkerHasher {
|
|
|
38
32
|
} = this.jobs[id];
|
|
39
33
|
delete this.jobs[id];
|
|
40
34
|
hasherWorker.activeJobs--;
|
|
41
|
-
|
|
42
35
|
if (event.data.error) {
|
|
43
36
|
// TODO previously we were just calling it again.
|
|
44
37
|
// this.calculateHashInWorker(chunk);
|
|
@@ -48,7 +41,6 @@ export class WorkerHasher {
|
|
|
48
41
|
}
|
|
49
42
|
}
|
|
50
43
|
}
|
|
51
|
-
|
|
52
44
|
calculateHashInWorker(blob) {
|
|
53
45
|
const jobId = uuidV4();
|
|
54
46
|
return new Promise((resolve, reject) => {
|
|
@@ -60,45 +52,38 @@ export class WorkerHasher {
|
|
|
60
52
|
this.dispatch(jobId, worker, blob);
|
|
61
53
|
});
|
|
62
54
|
}
|
|
63
|
-
|
|
64
55
|
dispatch(jobId, hasherWorker, chunkBlob) {
|
|
65
56
|
hasherWorker.activeJobs++;
|
|
66
57
|
const worker = hasherWorker.worker;
|
|
58
|
+
|
|
67
59
|
/*
|
|
68
60
|
* postMessage() with chunk blob in Safari results in the error
|
|
69
61
|
* "Failed to load resource: The operation could not be completed. (WebKitBlobResource error 1.)"
|
|
70
62
|
*
|
|
71
63
|
* To prevent it, we read the data from the blob using FileReader and pass it via postMessage to the worker.
|
|
72
64
|
*/
|
|
73
|
-
|
|
74
65
|
if (navigator.userAgent.indexOf('Safari') > -1 && navigator.userAgent.indexOf('Chrome') === -1) {
|
|
75
66
|
const rd = new FileReader();
|
|
76
|
-
|
|
77
67
|
rd.onload = () => {
|
|
78
68
|
worker.postMessage({
|
|
79
69
|
id: jobId,
|
|
80
70
|
data: rd.result
|
|
81
71
|
});
|
|
82
72
|
};
|
|
83
|
-
|
|
84
73
|
rd.readAsBinaryString(chunkBlob);
|
|
85
74
|
return;
|
|
86
75
|
}
|
|
87
|
-
|
|
88
76
|
worker.postMessage({
|
|
89
77
|
id: jobId,
|
|
90
78
|
data: chunkBlob
|
|
91
79
|
});
|
|
92
80
|
}
|
|
93
|
-
|
|
94
81
|
getMostRelaxedWorker() {
|
|
95
82
|
return this.workers.reduce((current, next) => {
|
|
96
83
|
if (next.activeJobs < current.activeJobs) {
|
|
97
84
|
return next;
|
|
98
85
|
}
|
|
99
|
-
|
|
100
86
|
return current;
|
|
101
87
|
}, this.workers[0]);
|
|
102
88
|
}
|
|
103
|
-
|
|
104
89
|
}
|
|
@@ -2,12 +2,10 @@
|
|
|
2
2
|
export const isImageRemote = (imageUrl, windowOrigin = window.location.origin) => {
|
|
3
3
|
if (URL && URL.prototype) {
|
|
4
4
|
const url = new URL(imageUrl);
|
|
5
|
-
|
|
6
5
|
if (!url.host) {
|
|
7
6
|
// This is a local resource. Safari will fail to load it if we set crossorigin
|
|
8
7
|
return false;
|
|
9
8
|
}
|
|
10
|
-
|
|
11
9
|
return url.origin !== windowOrigin;
|
|
12
10
|
} else {
|
|
13
11
|
// IE doesn't have support to 'new URL'.
|
|
@@ -2,7 +2,8 @@ import { createMediaSubject } from '../createMediaSubject';
|
|
|
2
2
|
export function fromObservable(observable) {
|
|
3
3
|
return {
|
|
4
4
|
subscribe: observer => {
|
|
5
|
-
const subscription =
|
|
5
|
+
const subscription =
|
|
6
|
+
// This is needed to handle "subscribe" function overload.
|
|
6
7
|
// It allows accepting a single "next" callback function as an argument.
|
|
7
8
|
observer instanceof Function ? observable.subscribe(observer) : observable.subscribe(observer);
|
|
8
9
|
return {
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { Subscription } from 'rxjs/Subscription';
|
|
2
|
-
|
|
3
2
|
/**
|
|
4
3
|
* This is a helper to transform the first value emitted by an MediaSubscribable into a Promise.
|
|
5
4
|
*
|
|
6
5
|
* @param mediaSubscribable a given MediaSubscribable<MediaSubscribableItem>
|
|
7
6
|
* @param subscription a default Subscription (this parameter exists for testing purpose)
|
|
8
7
|
*/
|
|
8
|
+
|
|
9
9
|
export const toPromise = (mediaSubscribable, subscription = new Subscription()) => {
|
|
10
10
|
return new Promise((resolve, reject) => subscription.add(mediaSubscribable.subscribe({
|
|
11
11
|
next: state => {
|
|
@@ -6,7 +6,6 @@ export class MobileUploadError extends BaseMediaClientError {
|
|
|
6
6
|
this.id = id;
|
|
7
7
|
this.metadata = metadata;
|
|
8
8
|
}
|
|
9
|
-
|
|
10
9
|
get attributes() {
|
|
11
10
|
const {
|
|
12
11
|
reason,
|
|
@@ -23,7 +22,6 @@ export class MobileUploadError extends BaseMediaClientError {
|
|
|
23
22
|
occurrenceKey
|
|
24
23
|
};
|
|
25
24
|
}
|
|
26
|
-
|
|
27
25
|
}
|
|
28
26
|
export function isMobileUploadError(err) {
|
|
29
27
|
return err instanceof MobileUploadError;
|
|
@@ -13,39 +13,34 @@ export const createMobileFileStateSubject = service => {
|
|
|
13
13
|
};
|
|
14
14
|
export const createMobileDownloadFileStream = (dataloader, id, collectionName, occurrenceKey) => {
|
|
15
15
|
const subject = createMediaSubject();
|
|
16
|
-
const poll = new PollingFunction();
|
|
16
|
+
const poll = new PollingFunction();
|
|
17
17
|
|
|
18
|
+
// ensure subject errors if polling exceeds max iterations or uncaught exception in executor
|
|
18
19
|
poll.onError = error => subject.error(error);
|
|
19
|
-
|
|
20
20
|
poll.execute(async () => {
|
|
21
21
|
const response = await dataloader.load({
|
|
22
22
|
id,
|
|
23
23
|
collectionName
|
|
24
24
|
});
|
|
25
|
-
|
|
26
25
|
if (!response) {
|
|
27
26
|
throw new MobileUploadError('emptyItems', id, {
|
|
28
27
|
collectionName,
|
|
29
28
|
occurrenceKey
|
|
30
29
|
});
|
|
31
30
|
}
|
|
32
|
-
|
|
33
31
|
if (isEmptyFile(response)) {
|
|
34
32
|
throw new MobileUploadError('zeroVersionFile', id, {
|
|
35
33
|
collectionName,
|
|
36
34
|
occurrenceKey
|
|
37
35
|
});
|
|
38
36
|
}
|
|
39
|
-
|
|
40
37
|
const fileState = mapMediaItemToFileState(id, response);
|
|
41
38
|
subject.next(fileState);
|
|
42
|
-
|
|
43
39
|
switch (fileState.status) {
|
|
44
40
|
case 'processing':
|
|
45
41
|
// the only case for continuing polling, otherwise this function is run once only
|
|
46
42
|
poll.next();
|
|
47
43
|
break;
|
|
48
|
-
|
|
49
44
|
case 'processed':
|
|
50
45
|
subject.complete();
|
|
51
46
|
break;
|
|
@@ -29,7 +29,6 @@ export const createMobileUploadStateMachine = (dataloader, initialState, collect
|
|
|
29
29
|
const {
|
|
30
30
|
currentFileState
|
|
31
31
|
} = ctx;
|
|
32
|
-
|
|
33
32
|
if (isProcessingFileState(currentFileState)) {
|
|
34
33
|
const {
|
|
35
34
|
mediaType,
|
|
@@ -38,7 +37,6 @@ export const createMobileUploadStateMachine = (dataloader, initialState, collect
|
|
|
38
37
|
} = currentFileState;
|
|
39
38
|
return shouldFetchRemoteFileStates(mediaType, mimeType, preview);
|
|
40
39
|
}
|
|
41
|
-
|
|
42
40
|
return false;
|
|
43
41
|
},
|
|
44
42
|
fetchRemoteFileStates: ctx => createMobileDownloadFileStream(dataloader, ctx.currentFileState.id, collectionName, ctx.currentFileState.occurrenceKey).pipe(map(fileState => ({
|
|
@@ -7,7 +7,8 @@ export const machineUploadingState = {
|
|
|
7
7
|
target: 'uploading',
|
|
8
8
|
cond: (ctx, event) => isUploadingFileState(ctx.currentFileState) && event.progress > ctx.currentFileState.progress,
|
|
9
9
|
actions: assign({
|
|
10
|
-
currentFileState: (ctx, event) => ({
|
|
10
|
+
currentFileState: (ctx, event) => ({
|
|
11
|
+
...ctx.currentFileState,
|
|
11
12
|
progress: event.progress
|
|
12
13
|
})
|
|
13
14
|
})
|
|
@@ -5,7 +5,6 @@ export class PollingError extends BaseMediaClientError {
|
|
|
5
5
|
this.reason = reason;
|
|
6
6
|
this.attempts = attempts;
|
|
7
7
|
}
|
|
8
|
-
|
|
9
8
|
get attributes() {
|
|
10
9
|
const {
|
|
11
10
|
reason,
|
|
@@ -16,7 +15,6 @@ export class PollingError extends BaseMediaClientError {
|
|
|
16
15
|
attempts
|
|
17
16
|
};
|
|
18
17
|
}
|
|
19
|
-
|
|
20
18
|
}
|
|
21
19
|
export function isPollingError(err) {
|
|
22
20
|
return err instanceof PollingError;
|
|
@@ -7,6 +7,7 @@ export const defaultPollingOptions = {
|
|
|
7
7
|
poll_backoffFactor: 1.25,
|
|
8
8
|
poll_maxIntervalMs: 200000
|
|
9
9
|
};
|
|
10
|
+
|
|
10
11
|
/**
|
|
11
12
|
* This class encapsulates polling functionality with the following features:
|
|
12
13
|
*
|
|
@@ -18,47 +19,37 @@ export const defaultPollingOptions = {
|
|
|
18
19
|
*
|
|
19
20
|
* IMPORTANT! the executor function must explicitly call ".next()" for the next iteration to run
|
|
20
21
|
*/
|
|
21
|
-
|
|
22
22
|
export class PollingFunction {
|
|
23
23
|
constructor(options) {
|
|
24
24
|
_defineProperty(this, "poll_intervalMs", 0);
|
|
25
|
-
|
|
26
25
|
_defineProperty(this, "attempt", 1);
|
|
27
|
-
|
|
28
26
|
_defineProperty(this, "shouldIterate", true);
|
|
29
|
-
|
|
30
27
|
_defineProperty(this, "timeoutId", 0);
|
|
31
|
-
|
|
32
|
-
|
|
28
|
+
this.options = {
|
|
29
|
+
...defaultPollingOptions,
|
|
33
30
|
...options
|
|
34
31
|
};
|
|
35
32
|
this.poll_intervalMs = this.options.poll_intervalMs;
|
|
36
33
|
}
|
|
37
|
-
|
|
38
34
|
async execute(executor) {
|
|
39
35
|
const {
|
|
40
36
|
poll_maxAttempts
|
|
41
37
|
} = this.options;
|
|
42
|
-
|
|
43
38
|
if (poll_maxAttempts === 0) {
|
|
44
39
|
// hard kill, polling disabled
|
|
45
40
|
return this.fail(new PollingError('pollingMaxAttemptsExceeded', this.attempt));
|
|
46
41
|
}
|
|
47
|
-
|
|
48
42
|
try {
|
|
49
43
|
// executor must explicitly call this.next() for triggering next iteration (pull)
|
|
50
44
|
this.shouldIterate = false;
|
|
51
45
|
await executor();
|
|
52
|
-
|
|
53
46
|
if (!this.shouldIterate) {
|
|
54
47
|
return;
|
|
55
48
|
}
|
|
56
|
-
|
|
57
49
|
if (this.attempt >= poll_maxAttempts) {
|
|
58
50
|
// max iterations exceeded, let the consumer know
|
|
59
51
|
return this.fail(new PollingError('pollingMaxAttemptsExceeded', this.attempt));
|
|
60
52
|
}
|
|
61
|
-
|
|
62
53
|
this.poll_intervalMs = this.getIntervalMsForIteration(this.attempt);
|
|
63
54
|
this.attempt++;
|
|
64
55
|
this.timeoutId = window.setTimeout(() => this.execute(executor), this.poll_intervalMs);
|
|
@@ -66,7 +57,6 @@ export class PollingFunction {
|
|
|
66
57
|
this.fail(error);
|
|
67
58
|
}
|
|
68
59
|
}
|
|
69
|
-
|
|
70
60
|
fail(error) {
|
|
71
61
|
const {
|
|
72
62
|
onError
|
|
@@ -74,28 +64,21 @@ export class PollingFunction {
|
|
|
74
64
|
this.cancel();
|
|
75
65
|
onError && onError(error);
|
|
76
66
|
}
|
|
77
|
-
|
|
78
67
|
getIntervalMsForIteration(iteration) {
|
|
79
68
|
let poll_intervalMs = this.options.poll_intervalMs;
|
|
80
|
-
|
|
81
69
|
if (iteration === 1) {
|
|
82
70
|
return poll_intervalMs;
|
|
83
71
|
}
|
|
84
|
-
|
|
85
72
|
for (let i = 2; i <= iteration; i++) {
|
|
86
73
|
poll_intervalMs = poll_intervalMs * this.options.poll_backoffFactor;
|
|
87
74
|
}
|
|
88
|
-
|
|
89
75
|
return Math.min(Math.round(poll_intervalMs), this.options.poll_maxIntervalMs);
|
|
90
76
|
}
|
|
91
|
-
|
|
92
77
|
next() {
|
|
93
78
|
this.shouldIterate = true;
|
|
94
79
|
}
|
|
95
|
-
|
|
96
80
|
cancel() {
|
|
97
81
|
window.clearTimeout(this.timeoutId);
|
|
98
82
|
this.timeoutId = 0;
|
|
99
83
|
}
|
|
100
|
-
|
|
101
84
|
}
|