@atlaskit/media-file-preview 0.0.1
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 +1 -0
- package/LICENSE.md +13 -0
- package/README.md +9 -0
- package/dist/cjs/analytics.js +50 -0
- package/dist/cjs/errors.js +143 -0
- package/dist/cjs/getPreview/cache.js +39 -0
- package/dist/cjs/getPreview/getPreview.js +119 -0
- package/dist/cjs/getPreview/helpers.js +167 -0
- package/dist/cjs/getPreview/index.js +56 -0
- package/dist/cjs/getPreview/objectURLCache.js +85 -0
- package/dist/cjs/getPreview/videoSnapshot.js +63 -0
- package/dist/cjs/globalScope/getSSRData.js +14 -0
- package/dist/cjs/globalScope/globalScope.js +66 -0
- package/dist/cjs/globalScope/index.js +37 -0
- package/dist/cjs/globalScope/printScript.js +32 -0
- package/dist/cjs/globalScope/types.js +5 -0
- package/dist/cjs/helpers.js +56 -0
- package/dist/cjs/index.js +12 -0
- package/dist/cjs/types.js +5 -0
- package/dist/cjs/useFilePreview.js +355 -0
- package/dist/es2019/analytics.js +44 -0
- package/dist/es2019/errors.js +90 -0
- package/dist/es2019/getPreview/cache.js +30 -0
- package/dist/es2019/getPreview/getPreview.js +75 -0
- package/dist/es2019/getPreview/helpers.js +77 -0
- package/dist/es2019/getPreview/index.js +3 -0
- package/dist/es2019/getPreview/objectURLCache.js +44 -0
- package/dist/es2019/getPreview/videoSnapshot.js +41 -0
- package/dist/es2019/globalScope/getSSRData.js +8 -0
- package/dist/es2019/globalScope/globalScope.js +48 -0
- package/dist/es2019/globalScope/index.js +2 -0
- package/dist/es2019/globalScope/printScript.js +16 -0
- package/dist/es2019/globalScope/types.js +1 -0
- package/dist/es2019/helpers.js +53 -0
- package/dist/es2019/index.js +1 -0
- package/dist/es2019/types.js +1 -0
- package/dist/es2019/useFilePreview.js +333 -0
- package/dist/esm/analytics.js +44 -0
- package/dist/esm/errors.js +133 -0
- package/dist/esm/getPreview/cache.js +32 -0
- package/dist/esm/getPreview/getPreview.js +112 -0
- package/dist/esm/getPreview/helpers.js +161 -0
- package/dist/esm/getPreview/index.js +3 -0
- package/dist/esm/getPreview/objectURLCache.js +78 -0
- package/dist/esm/getPreview/videoSnapshot.js +56 -0
- package/dist/esm/globalScope/getSSRData.js +8 -0
- package/dist/esm/globalScope/globalScope.js +56 -0
- package/dist/esm/globalScope/index.js +2 -0
- package/dist/esm/globalScope/printScript.js +25 -0
- package/dist/esm/globalScope/types.js +1 -0
- package/dist/esm/helpers.js +49 -0
- package/dist/esm/index.js +1 -0
- package/dist/esm/types.js +1 -0
- package/dist/esm/useFilePreview.js +348 -0
- package/dist/types/analytics.d.ts +28 -0
- package/dist/types/errors.d.ts +42 -0
- package/dist/types/getPreview/cache.d.ts +21 -0
- package/dist/types/getPreview/getPreview.d.ts +9 -0
- package/dist/types/getPreview/helpers.d.ts +10 -0
- package/dist/types/getPreview/index.d.ts +3 -0
- package/dist/types/getPreview/objectURLCache.d.ts +12 -0
- package/dist/types/getPreview/videoSnapshot.d.ts +1 -0
- package/dist/types/globalScope/getSSRData.d.ts +3 -0
- package/dist/types/globalScope/globalScope.d.ts +15 -0
- package/dist/types/globalScope/index.d.ts +4 -0
- package/dist/types/globalScope/printScript.d.ts +2 -0
- package/dist/types/globalScope/types.d.ts +8 -0
- package/dist/types/helpers.d.ts +10 -0
- package/dist/types/index.d.ts +2 -0
- package/dist/types/types.d.ts +12 -0
- package/dist/types/useFilePreview.d.ts +33 -0
- package/dist/types-ts4.5/analytics.d.ts +28 -0
- package/dist/types-ts4.5/errors.d.ts +42 -0
- package/dist/types-ts4.5/getPreview/cache.d.ts +21 -0
- package/dist/types-ts4.5/getPreview/getPreview.d.ts +9 -0
- package/dist/types-ts4.5/getPreview/helpers.d.ts +10 -0
- package/dist/types-ts4.5/getPreview/index.d.ts +3 -0
- package/dist/types-ts4.5/getPreview/objectURLCache.d.ts +12 -0
- package/dist/types-ts4.5/getPreview/videoSnapshot.d.ts +1 -0
- package/dist/types-ts4.5/globalScope/getSSRData.d.ts +3 -0
- package/dist/types-ts4.5/globalScope/globalScope.d.ts +15 -0
- package/dist/types-ts4.5/globalScope/index.d.ts +4 -0
- package/dist/types-ts4.5/globalScope/printScript.d.ts +2 -0
- package/dist/types-ts4.5/globalScope/types.d.ts +8 -0
- package/dist/types-ts4.5/helpers.d.ts +10 -0
- package/dist/types-ts4.5/index.d.ts +2 -0
- package/dist/types-ts4.5/types.d.ts +12 -0
- package/dist/types-ts4.5/useFilePreview.d.ts +33 -0
- package/package.json +98 -0
|
@@ -0,0 +1,355 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
|
|
4
|
+
Object.defineProperty(exports, "__esModule", {
|
|
5
|
+
value: true
|
|
6
|
+
});
|
|
7
|
+
exports.useFilePreview = void 0;
|
|
8
|
+
var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty"));
|
|
9
|
+
var _slicedToArray2 = _interopRequireDefault(require("@babel/runtime/helpers/slicedToArray"));
|
|
10
|
+
var _react = require("react");
|
|
11
|
+
var _mediaClient = require("@atlaskit/media-client");
|
|
12
|
+
var _mediaClientReact = require("@atlaskit/media-client-react");
|
|
13
|
+
var _mediaCommon = require("@atlaskit/media-common");
|
|
14
|
+
var _analytics = require("./analytics");
|
|
15
|
+
var _errors = require("./errors");
|
|
16
|
+
var _getPreview = require("./getPreview");
|
|
17
|
+
var _globalScope = require("./globalScope");
|
|
18
|
+
var _helpers = require("./helpers");
|
|
19
|
+
function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
|
|
20
|
+
function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { (0, _defineProperty2.default)(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
|
|
21
|
+
var useFilePreview = exports.useFilePreview = function useFilePreview(_ref) {
|
|
22
|
+
var _ref$resizeMode = _ref.resizeMode,
|
|
23
|
+
resizeMode = _ref$resizeMode === void 0 ? 'crop' : _ref$resizeMode,
|
|
24
|
+
identifier = _ref.identifier,
|
|
25
|
+
ssr = _ref.ssr,
|
|
26
|
+
dimensions = _ref.dimensions,
|
|
27
|
+
traceContext = _ref.traceContext,
|
|
28
|
+
previewDidRender = _ref.previewDidRender,
|
|
29
|
+
skipRemote = _ref.skipRemote,
|
|
30
|
+
mediaBlobUrlAttrs = _ref.mediaBlobUrlAttrs;
|
|
31
|
+
var mediaClient = (0, _mediaClientReact.useMediaClient)();
|
|
32
|
+
var _useState = (0, _react.useState)('loading'),
|
|
33
|
+
_useState2 = (0, _slicedToArray2.default)(_useState, 2),
|
|
34
|
+
status = _useState2[0],
|
|
35
|
+
setStatus = _useState2[1];
|
|
36
|
+
var _useState3 = (0, _react.useState)(),
|
|
37
|
+
_useState4 = (0, _slicedToArray2.default)(_useState3, 2),
|
|
38
|
+
error = _useState4[0],
|
|
39
|
+
setError = _useState4[1];
|
|
40
|
+
var _useState5 = (0, _react.useState)(),
|
|
41
|
+
_useState6 = (0, _slicedToArray2.default)(_useState5, 2),
|
|
42
|
+
nonCriticalError = _useState6[0],
|
|
43
|
+
setNonCriticalError = _useState6[1];
|
|
44
|
+
var _useState7 = (0, _react.useState)(false),
|
|
45
|
+
_useState8 = (0, _slicedToArray2.default)(_useState7, 2),
|
|
46
|
+
isBannedLocalPreview = _useState8[0],
|
|
47
|
+
setIsBannedLocalPreview = _useState8[1];
|
|
48
|
+
var wasResolvedUpfrontPreviewRef = (0, _react.useRef)(false);
|
|
49
|
+
var ssrReliabilityRef = (0, _react.useRef)(initialSsrReliability);
|
|
50
|
+
var requestDimensions = (0, _react.useMemo)(function () {
|
|
51
|
+
return dimensions ? (0, _helpers.createRequestDimensions)(dimensions) : undefined;
|
|
52
|
+
}, [dimensions]);
|
|
53
|
+
var requestDimensionsRef = (0, _helpers.useCurrentValueRef)(requestDimensions);
|
|
54
|
+
var imageURLParams = (0, _react.useMemo)(function () {
|
|
55
|
+
return _objectSpread(_objectSpread({
|
|
56
|
+
collection: identifier.collectionName,
|
|
57
|
+
mode: resizeMode === 'stretchy-fit' ? 'full-fit' : resizeMode
|
|
58
|
+
}, requestDimensions), {}, {
|
|
59
|
+
allowAnimated: true
|
|
60
|
+
});
|
|
61
|
+
}, [requestDimensions, identifier.collectionName, resizeMode]);
|
|
62
|
+
var previewInitializer = function previewInitializer() {
|
|
63
|
+
var fileImageMode = (0, _mediaClient.imageResizeModeToFileImageMode)(resizeMode);
|
|
64
|
+
var preview = _getPreview.mediaFilePreviewCache.get(identifier.id, fileImageMode);
|
|
65
|
+
if (preview) {
|
|
66
|
+
return preview;
|
|
67
|
+
}
|
|
68
|
+
if (ssr) {
|
|
69
|
+
var ssrData = (0, _globalScope.getSSRData)(identifier);
|
|
70
|
+
if (ssrData !== null && ssrData !== void 0 && ssrData.error) {
|
|
71
|
+
ssrReliabilityRef.current.server = _objectSpread({
|
|
72
|
+
status: 'fail'
|
|
73
|
+
}, ssrData.error);
|
|
74
|
+
}
|
|
75
|
+
if (!(ssrData !== null && ssrData !== void 0 && ssrData.dataURI)) {
|
|
76
|
+
try {
|
|
77
|
+
return (0, _getPreview.getSSRPreview)(ssr, mediaClient, identifier.id, imageURLParams, mediaBlobUrlAttrs);
|
|
78
|
+
} catch (e) {
|
|
79
|
+
ssrReliabilityRef.current[ssr] = _objectSpread({
|
|
80
|
+
status: 'fail'
|
|
81
|
+
}, (0, _analytics.extractErrorInfo)(e));
|
|
82
|
+
}
|
|
83
|
+
} else {
|
|
84
|
+
var _dimensions = ssrData.dimensions,
|
|
85
|
+
dataURI = ssrData.dataURI;
|
|
86
|
+
return {
|
|
87
|
+
dataURI: dataURI,
|
|
88
|
+
dimensions: _dimensions,
|
|
89
|
+
source: 'ssr-data'
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
};
|
|
94
|
+
var _useState9 = (0, _react.useState)(previewInitializer),
|
|
95
|
+
_useState10 = (0, _slicedToArray2.default)(_useState9, 2),
|
|
96
|
+
preview = _useState10[0],
|
|
97
|
+
setPreview = _useState10[1];
|
|
98
|
+
var _useFileState = (0, _mediaClientReact.useFileState)(identifier.id, {
|
|
99
|
+
skipRemote: skipRemote,
|
|
100
|
+
collectionName: identifier.collectionName,
|
|
101
|
+
occurrenceKey: identifier.occurrenceKey
|
|
102
|
+
}),
|
|
103
|
+
fileState = _useFileState.fileState;
|
|
104
|
+
|
|
105
|
+
//----------------------------------------------------------------
|
|
106
|
+
// Update status
|
|
107
|
+
//----------------------------------------------------------------
|
|
108
|
+
|
|
109
|
+
// TOOD: make a full hook reset (remount) on New identifier or client
|
|
110
|
+
(0, _react.useEffect)(function () {
|
|
111
|
+
setStatus('loading');
|
|
112
|
+
}, [identifier]);
|
|
113
|
+
var updateFileStateRef = (0, _helpers.useCurrentValueRef)(function () {
|
|
114
|
+
if (fileState) {
|
|
115
|
+
// do not update the status if the status is final
|
|
116
|
+
if (['complete', 'error', 'failed-processing'].includes(status)) {
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
if (fileState.status !== 'error') {
|
|
120
|
+
var mediaType = 'mediaType' in fileState ? fileState.mediaType : undefined;
|
|
121
|
+
var isPreviewable = !!mediaType && ['audio', 'video', 'image', 'doc'].indexOf(mediaType) > -1;
|
|
122
|
+
var isPreviewableFileState = !!fileState.preview;
|
|
123
|
+
var _isSupportedLocalPreview = mediaType === 'image' || mediaType === 'video';
|
|
124
|
+
var hasLocalPreview = !isBannedLocalPreview && isPreviewableFileState && _isSupportedLocalPreview && !!fileState.mimeType && (0, _mediaCommon.isMimeTypeSupportedByBrowser)(fileState.mimeType);
|
|
125
|
+
var hasRemotePreview = (0, _mediaClient.isImageRepresentationReady)(fileState);
|
|
126
|
+
var hasPreview = hasLocalPreview || hasRemotePreview;
|
|
127
|
+
var newStatus;
|
|
128
|
+
switch (fileState.status) {
|
|
129
|
+
case 'uploading':
|
|
130
|
+
case 'failed-processing':
|
|
131
|
+
case 'processing':
|
|
132
|
+
newStatus = fileState.status;
|
|
133
|
+
break;
|
|
134
|
+
case 'processed':
|
|
135
|
+
if (!isPreviewable || !hasPreview) {
|
|
136
|
+
newStatus = 'complete';
|
|
137
|
+
break;
|
|
138
|
+
}
|
|
139
|
+
newStatus = 'loading-preview';
|
|
140
|
+
break;
|
|
141
|
+
default:
|
|
142
|
+
newStatus = 'loading';
|
|
143
|
+
}
|
|
144
|
+
setStatus(newStatus);
|
|
145
|
+
} else {
|
|
146
|
+
var e = new _mediaClientReact.MediaFileStateError(fileState.id, fileState.reason, fileState.message, fileState.details);
|
|
147
|
+
var errorReason = status === 'uploading' ? 'upload' : 'metadata-fetch';
|
|
148
|
+
setError(new _errors.MediaFilePreviewError(errorReason, e));
|
|
149
|
+
setStatus('error');
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
});
|
|
153
|
+
(0, _react.useEffect)(function () {
|
|
154
|
+
updateFileStateRef.current();
|
|
155
|
+
}, [fileState, updateFileStateRef]);
|
|
156
|
+
(0, _react.useEffect)(function () {
|
|
157
|
+
if (previewDidRender &&
|
|
158
|
+
// We should't complete if status is uploading
|
|
159
|
+
['loading-preview', 'processing'].includes(status)) {
|
|
160
|
+
setStatus('complete');
|
|
161
|
+
// TODO MEX-788: add test for "do not remove the preview when unsubscribing".
|
|
162
|
+
setIsBannedLocalPreview(false); // CXP-2723 TODO: we might be able to remove this??
|
|
163
|
+
}
|
|
164
|
+
}, [previewDidRender, status]);
|
|
165
|
+
|
|
166
|
+
// CXP-2723 TODO: Create test cases for banning local preview after status is complete
|
|
167
|
+
|
|
168
|
+
//----------------------------------------------------------------
|
|
169
|
+
// Preview Fetch Helper
|
|
170
|
+
//----------------------------------------------------------------
|
|
171
|
+
var getAndCacheRemotePreviewRef = (0, _helpers.useCurrentValueRef)(function () {
|
|
172
|
+
return (0, _getPreview.getAndCacheRemotePreview)(mediaClient, identifier.id, requestDimensions || {}, imageURLParams, mediaBlobUrlAttrs, traceContext);
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
//----------------------------------------------------------------
|
|
176
|
+
// Cache SSR Preview
|
|
177
|
+
//----------------------------------------------------------------
|
|
178
|
+
(0, _react.useEffect)(function () {
|
|
179
|
+
if (!skipRemote && ssr && !!preview && (0, _getPreview.isSSRClientPreview)(preview)) {
|
|
180
|
+
// Since the SSR preview brings the token in the query params,
|
|
181
|
+
// We need to fetch the remote preview to be able to cache it,
|
|
182
|
+
getAndCacheRemotePreviewRef.current().catch(function () {
|
|
183
|
+
// No need to log this error.
|
|
184
|
+
// If preview fails, it will be refetched later
|
|
185
|
+
//TODO: test this catch
|
|
186
|
+
// https://product-fabric.atlassian.net/browse/MEX-1071
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
}, [getAndCacheRemotePreviewRef, preview, skipRemote, ssr]);
|
|
190
|
+
|
|
191
|
+
//----------------------------------------------------------------
|
|
192
|
+
// Refetch SRR Preview if dimensions from Server have changed and are bigger,
|
|
193
|
+
//----------------------------------------------------------------
|
|
194
|
+
(0, _react.useEffect)(function () {
|
|
195
|
+
// CXP-2813 TODO: This is called too many times if the refetch failed. Should be called only once
|
|
196
|
+
if (preview && !skipRemote && (0, _getPreview.isSSRDataPreview)(preview) && (0, _helpers.isBigger)(preview.dimensions, requestDimensions)) {
|
|
197
|
+
getAndCacheRemotePreviewRef.current().then(setPreview).catch(function (e) {
|
|
198
|
+
var wrappedError = (0, _errors.ensureMediaFilePreviewError)('remote-preview-fetch-ssr', e, true);
|
|
199
|
+
setNonCriticalError(wrappedError);
|
|
200
|
+
});
|
|
201
|
+
}
|
|
202
|
+
}, [getAndCacheRemotePreviewRef, preview, requestDimensions, skipRemote]);
|
|
203
|
+
|
|
204
|
+
//----------------------------------------------------------------
|
|
205
|
+
// Upfront Preview
|
|
206
|
+
//----------------------------------------------------------------
|
|
207
|
+
(0, _react.useEffect)(function () {
|
|
208
|
+
if (!preview && !wasResolvedUpfrontPreviewRef.current && !skipRemote) {
|
|
209
|
+
// We block any possible future call to this method regardless of the outcome (success or fail)
|
|
210
|
+
// If it fails, the normal preview fetch should occur after the file state is fetched anyways
|
|
211
|
+
wasResolvedUpfrontPreviewRef.current = true;
|
|
212
|
+
var fetchedDimensions = _objectSpread({}, requestDimensions);
|
|
213
|
+
getAndCacheRemotePreviewRef.current().then(function (newPreview) {
|
|
214
|
+
// If there are new and bigger dimensions in the props, and the upfront preview is still resolving,
|
|
215
|
+
// the fetched preview is no longer valid, and thus, we dismiss it
|
|
216
|
+
if (!(0, _helpers.isBigger)(fetchedDimensions, requestDimensionsRef.current)) {
|
|
217
|
+
setPreview(newPreview);
|
|
218
|
+
}
|
|
219
|
+
}).catch(function () {
|
|
220
|
+
// NO need to log error. If this call fails, a refetch will happen after
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
}, [getAndCacheRemotePreviewRef, preview, requestDimensions, requestDimensionsRef, skipRemote]);
|
|
224
|
+
|
|
225
|
+
//----------------------------------------------------------------
|
|
226
|
+
// Cache, Local & Remote Preview
|
|
227
|
+
//----------------------------------------------------------------
|
|
228
|
+
|
|
229
|
+
(0, _react.useEffect)(function () {
|
|
230
|
+
var cachedPreview = _getPreview.mediaFilePreviewCache.get(identifier.id, imageURLParams.mode);
|
|
231
|
+
|
|
232
|
+
// Cached Preview ----------------------------------------------------------------
|
|
233
|
+
if (!preview && cachedPreview && !(0, _helpers.isBigger)(cachedPreview === null || cachedPreview === void 0 ? void 0 : cachedPreview.dimensions, requestDimensions)) {
|
|
234
|
+
setPreview(cachedPreview);
|
|
235
|
+
}
|
|
236
|
+
// Local Preview ----------------------------------------------------------------
|
|
237
|
+
else if (!preview && !isBannedLocalPreview && !!fileState && 'preview' in fileState && !!fileState.preview && (0, _getPreview.isSupportedLocalPreview)(fileState.mediaType) && (0, _mediaCommon.isMimeTypeSupportedByBrowser)(fileState.mimeType)) {
|
|
238
|
+
// Local preview is available only if it's supported by browser and supported by Media Card (isSupportedLocalPreview)
|
|
239
|
+
// For example, SVGs are mime type NOT supported by browser but media type supported by Media Card (image)
|
|
240
|
+
// Then, local Preview NOT available
|
|
241
|
+
|
|
242
|
+
(0, _getPreview.getAndCacheLocalPreview)(identifier.id, fileState.preview, requestDimensions || {}, imageURLParams.mode, mediaBlobUrlAttrs).then(setPreview).catch(function (e) {
|
|
243
|
+
setIsBannedLocalPreview(true);
|
|
244
|
+
// CXP-2723 TODO: We might have to wrap this error in MediaCardError
|
|
245
|
+
setNonCriticalError(e);
|
|
246
|
+
});
|
|
247
|
+
}
|
|
248
|
+
// Remote Preview ----------------------------------------------------------------
|
|
249
|
+
else if ((!preview || (0, _helpers.isBigger)(preview.dimensions, requestDimensions)) && !skipRemote && wasResolvedUpfrontPreviewRef.current && !!fileState && (0, _mediaClient.isImageRepresentationReady)(fileState)) {
|
|
250
|
+
getAndCacheRemotePreviewRef.current().then(setPreview).catch(function (e) {
|
|
251
|
+
setStatus('error');
|
|
252
|
+
setError((0, _errors.ensureMediaFilePreviewError)('preview-fetch', e));
|
|
253
|
+
});
|
|
254
|
+
}
|
|
255
|
+
}, [fileState, getAndCacheRemotePreviewRef, identifier.id, imageURLParams.mode, isBannedLocalPreview, mediaBlobUrlAttrs, preview, requestDimensions, skipRemote]);
|
|
256
|
+
|
|
257
|
+
//----------------------------------------------------------------
|
|
258
|
+
// RETURN
|
|
259
|
+
//----------------------------------------------------------------
|
|
260
|
+
|
|
261
|
+
var onImageError = (0, _react.useCallback)(function (newPreview) {
|
|
262
|
+
if (newPreview) {
|
|
263
|
+
var failedSSRObject = _objectSpread({
|
|
264
|
+
status: 'fail'
|
|
265
|
+
}, (0, _analytics.extractErrorInfo)(new _errors.ImageLoadError(newPreview.source)));
|
|
266
|
+
if ((0, _getPreview.isSSRClientPreview)(newPreview)) {
|
|
267
|
+
ssrReliabilityRef.current.client = failedSSRObject;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
/*
|
|
271
|
+
If the preview failed and it comes from server (global scope / ssrData), it means that we have reused it in client and the error counts for both: server & client.
|
|
272
|
+
*/
|
|
273
|
+
|
|
274
|
+
if ((0, _getPreview.isSSRDataPreview)(newPreview)) {
|
|
275
|
+
ssrReliabilityRef.current.server = failedSSRObject;
|
|
276
|
+
ssrReliabilityRef.current.client = failedSSRObject;
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
// If the dataURI has been replaced, we can dismiss this error
|
|
281
|
+
if ((newPreview === null || newPreview === void 0 ? void 0 : newPreview.dataURI) !== (preview === null || preview === void 0 ? void 0 : preview.dataURI)) {
|
|
282
|
+
return;
|
|
283
|
+
}
|
|
284
|
+
var error = new _errors.ImageLoadError(newPreview === null || newPreview === void 0 ? void 0 : newPreview.source);
|
|
285
|
+
var isLocal = newPreview && (0, _getPreview.isLocalPreview)(newPreview);
|
|
286
|
+
var isSSR = newPreview && ((0, _getPreview.isSSRClientPreview)(newPreview) || (0, _getPreview.isSSRDataPreview)(newPreview));
|
|
287
|
+
if (isLocal || isSSR) {
|
|
288
|
+
if (isLocal) {
|
|
289
|
+
setIsBannedLocalPreview(true);
|
|
290
|
+
setNonCriticalError(error);
|
|
291
|
+
}
|
|
292
|
+
var fileImageMode = (0, _mediaClient.imageResizeModeToFileImageMode)(resizeMode);
|
|
293
|
+
_getPreview.mediaFilePreviewCache.remove(identifier.id, fileImageMode);
|
|
294
|
+
setPreview(undefined);
|
|
295
|
+
} else {
|
|
296
|
+
if (!['complete', 'error', 'failed-processing'].includes(status)) {
|
|
297
|
+
setStatus('error');
|
|
298
|
+
setError(error);
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
}, [identifier.id, preview === null || preview === void 0 ? void 0 : preview.dataURI, resizeMode, status]);
|
|
302
|
+
var onImageLoad = (0, _react.useCallback)(function (newPreview) {
|
|
303
|
+
if (newPreview) {
|
|
304
|
+
if ((0, _getPreview.isSSRClientPreview)(newPreview) && ssrReliabilityRef.current.client.status === 'unknown') {
|
|
305
|
+
ssrReliabilityRef.current.client = {
|
|
306
|
+
status: 'success'
|
|
307
|
+
};
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
/*
|
|
311
|
+
If the image loads successfully and it comes from server (global scope / ssrData), it means that we have reused it in client and the success counts for both: server & client.
|
|
312
|
+
*/
|
|
313
|
+
|
|
314
|
+
if ((0, _getPreview.isSSRDataPreview)(newPreview) && ssrReliabilityRef.current.server.status === 'unknown') {
|
|
315
|
+
ssrReliabilityRef.current.server = {
|
|
316
|
+
status: 'success'
|
|
317
|
+
};
|
|
318
|
+
ssrReliabilityRef.current.client = {
|
|
319
|
+
status: 'success'
|
|
320
|
+
};
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
// If the dataURI has been replaced, we can dismiss this callback
|
|
325
|
+
if ((newPreview === null || newPreview === void 0 ? void 0 : newPreview.dataURI) !== (preview === null || preview === void 0 ? void 0 : preview.dataURI)) {
|
|
326
|
+
return;
|
|
327
|
+
}
|
|
328
|
+
}, [preview === null || preview === void 0 ? void 0 : preview.dataURI]);
|
|
329
|
+
|
|
330
|
+
// FOR SSR
|
|
331
|
+
var getScriptProps = function getScriptProps() {
|
|
332
|
+
var _ssrReliabilityRef$cu;
|
|
333
|
+
return (0, _globalScope.generateScriptProps)(identifier, preview === null || preview === void 0 ? void 0 : preview.dataURI, requestDimensions, ((_ssrReliabilityRef$cu = ssrReliabilityRef.current.server) === null || _ssrReliabilityRef$cu === void 0 ? void 0 : _ssrReliabilityRef$cu.status) === 'fail' ? ssrReliabilityRef.current.server : undefined);
|
|
334
|
+
};
|
|
335
|
+
|
|
336
|
+
// CXP-2723 TODO: should consider simplifying our analytics, and how
|
|
337
|
+
// we might get rid of ssrReliabiltyRef from our hook
|
|
338
|
+
return {
|
|
339
|
+
preview: preview,
|
|
340
|
+
error: error,
|
|
341
|
+
nonCriticalError: nonCriticalError,
|
|
342
|
+
ssrReliabilityRef: ssrReliabilityRef,
|
|
343
|
+
onImageError: onImageError,
|
|
344
|
+
onImageLoad: onImageLoad,
|
|
345
|
+
getScriptProps: getScriptProps
|
|
346
|
+
};
|
|
347
|
+
};
|
|
348
|
+
var initialSsrReliability = {
|
|
349
|
+
server: {
|
|
350
|
+
status: 'unknown'
|
|
351
|
+
},
|
|
352
|
+
client: {
|
|
353
|
+
status: 'unknown'
|
|
354
|
+
}
|
|
355
|
+
};
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { getMediaClientErrorReason, isRequestError } from '@atlaskit/media-client';
|
|
2
|
+
import { getFileStateErrorReason, isMediaFilePreviewError, isMediaFileStateError } from './errors';
|
|
3
|
+
export const getErrorTraceContext = error => {
|
|
4
|
+
if (isMediaFilePreviewError(error) && !!error.secondaryError) {
|
|
5
|
+
if (isRequestError(error.secondaryError)) {
|
|
6
|
+
var _error$secondaryError;
|
|
7
|
+
return (_error$secondaryError = error.secondaryError.metadata) === null || _error$secondaryError === void 0 ? void 0 : _error$secondaryError.traceContext;
|
|
8
|
+
} else if (isMediaFileStateError(error.secondaryError)) {
|
|
9
|
+
var _error$secondaryError2, _error$secondaryError3;
|
|
10
|
+
return (_error$secondaryError2 = error.secondaryError.details) === null || _error$secondaryError2 === void 0 ? void 0 : (_error$secondaryError3 = _error$secondaryError2.metadata) === null || _error$secondaryError3 === void 0 ? void 0 : _error$secondaryError3.traceContext;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
};
|
|
14
|
+
export const getRenderErrorFailReason = error => {
|
|
15
|
+
if (isMediaFilePreviewError(error)) {
|
|
16
|
+
return error.primaryReason;
|
|
17
|
+
} else {
|
|
18
|
+
return 'nativeError';
|
|
19
|
+
}
|
|
20
|
+
};
|
|
21
|
+
export const getRenderErrorErrorReason = error => {
|
|
22
|
+
if (isMediaFilePreviewError(error) && error.secondaryError) {
|
|
23
|
+
const mediaClientReason = isMediaFileStateError(error.secondaryError) ? getFileStateErrorReason(error.secondaryError) : getMediaClientErrorReason(error.secondaryError);
|
|
24
|
+
if (mediaClientReason !== 'unknown') {
|
|
25
|
+
return mediaClientReason;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
return 'nativeError';
|
|
29
|
+
};
|
|
30
|
+
export const getRenderErrorErrorDetail = error => {
|
|
31
|
+
if (isMediaFilePreviewError(error) && error.secondaryError) {
|
|
32
|
+
return error.secondaryError.message;
|
|
33
|
+
} else {
|
|
34
|
+
return error.message;
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
export const extractErrorInfo = (error, metadataTraceContext) => {
|
|
38
|
+
return {
|
|
39
|
+
failReason: getRenderErrorFailReason(error),
|
|
40
|
+
error: getRenderErrorErrorReason(error),
|
|
41
|
+
errorDetail: getRenderErrorErrorDetail(error),
|
|
42
|
+
metadataTraceContext: metadataTraceContext !== null && metadataTraceContext !== void 0 ? metadataTraceContext : getErrorTraceContext(error)
|
|
43
|
+
};
|
|
44
|
+
};
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { MediaFileStateError } from '@atlaskit/media-client-react';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Primary reason is logged through Data Portal.
|
|
5
|
+
* Make sure all the values are whitelisted in Measure -> Event Regitry -> "mediaCardRender failed" event
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
export class MediaFilePreviewError extends Error {
|
|
9
|
+
constructor(primaryReason, secondaryError) {
|
|
10
|
+
super(primaryReason);
|
|
11
|
+
// https://www.typescriptlang.org/docs/handbook/release-notes/typescript-2-2.html#support-for-newtarget
|
|
12
|
+
this.primaryReason = primaryReason;
|
|
13
|
+
this.secondaryError = secondaryError;
|
|
14
|
+
Object.setPrototypeOf(this, new.target.prototype);
|
|
15
|
+
|
|
16
|
+
// https://v8.dev/docs/stack-trace-api
|
|
17
|
+
if ('captureStackTrace' in Error) {
|
|
18
|
+
Error.captureStackTrace(this, new.target);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
export class LocalPreviewError extends MediaFilePreviewError {
|
|
23
|
+
constructor(primaryReason, secondaryError) {
|
|
24
|
+
super(primaryReason, secondaryError);
|
|
25
|
+
this.primaryReason = primaryReason;
|
|
26
|
+
this.secondaryError = secondaryError;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
export class RemotePreviewError extends MediaFilePreviewError {
|
|
30
|
+
constructor(primaryReason, secondaryError) {
|
|
31
|
+
super(primaryReason, secondaryError);
|
|
32
|
+
this.primaryReason = primaryReason;
|
|
33
|
+
this.secondaryError = secondaryError;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
export class SsrPreviewError extends MediaFilePreviewError {
|
|
37
|
+
constructor(primaryReason, secondaryError) {
|
|
38
|
+
super(primaryReason, secondaryError);
|
|
39
|
+
this.primaryReason = primaryReason;
|
|
40
|
+
this.secondaryError = secondaryError;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
const getImageLoadPrimaryReason = source => {
|
|
44
|
+
switch (source) {
|
|
45
|
+
case 'cache-remote':
|
|
46
|
+
return 'cache-remote-uri';
|
|
47
|
+
case 'cache-local':
|
|
48
|
+
return 'cache-local-uri';
|
|
49
|
+
case 'external':
|
|
50
|
+
return 'external-uri';
|
|
51
|
+
case 'local':
|
|
52
|
+
return 'local-uri';
|
|
53
|
+
case 'remote':
|
|
54
|
+
return 'remote-uri';
|
|
55
|
+
// This fail reason will come from a bug, most likely.
|
|
56
|
+
default:
|
|
57
|
+
return `unknown-uri`;
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
export class ImageLoadError extends MediaFilePreviewError {
|
|
61
|
+
constructor(source) {
|
|
62
|
+
super(getImageLoadPrimaryReason(source));
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
export function isMediaFilePreviewError(err) {
|
|
66
|
+
return err instanceof MediaFilePreviewError;
|
|
67
|
+
}
|
|
68
|
+
export const isLocalPreviewError = err => err instanceof LocalPreviewError;
|
|
69
|
+
export const isRemotePreviewError = err => err instanceof RemotePreviewError;
|
|
70
|
+
export const isUnsupportedLocalPreviewError = err => isMediaFilePreviewError(err) && err.primaryReason === 'local-preview-unsupported';
|
|
71
|
+
|
|
72
|
+
// In a try/catch statement, the error caught is the type of unknown.
|
|
73
|
+
// We can use this helper to ensure that the error handled is the type of MediaFilePreviewError if unsure
|
|
74
|
+
// If updatePrimaryReason is true, if it's a MediaFilePreviewError already, it will update it's primary reason
|
|
75
|
+
export const ensureMediaFilePreviewError = (primaryReason, error, updatePrimaryReason) => {
|
|
76
|
+
if (isMediaFilePreviewError(error)) {
|
|
77
|
+
if (updatePrimaryReason && error.primaryReason !== primaryReason) {
|
|
78
|
+
return new MediaFilePreviewError(primaryReason, error.secondaryError);
|
|
79
|
+
}
|
|
80
|
+
return error;
|
|
81
|
+
}
|
|
82
|
+
return new MediaFilePreviewError(primaryReason, error);
|
|
83
|
+
};
|
|
84
|
+
export function isMediaFileStateError(err) {
|
|
85
|
+
return err instanceof MediaFileStateError;
|
|
86
|
+
}
|
|
87
|
+
export function getFileStateErrorReason(err) {
|
|
88
|
+
var _err$details$reason, _err$details;
|
|
89
|
+
return (_err$details$reason = (_err$details = err.details) === null || _err$details === void 0 ? void 0 : _err$details.reason) !== null && _err$details$reason !== void 0 ? _err$details$reason : 'unknown';
|
|
90
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import _defineProperty from "@babel/runtime/helpers/defineProperty";
|
|
2
|
+
import { createObjectURLCache } from './objectURLCache';
|
|
3
|
+
|
|
4
|
+
// Dimensions are used to create a key.
|
|
5
|
+
// Cache is invalidated when different dimensions are provided.
|
|
6
|
+
export const getCacheKey = (id, mode) => {
|
|
7
|
+
const resizeMode = mode || 'crop';
|
|
8
|
+
return [id, resizeMode].join('-');
|
|
9
|
+
};
|
|
10
|
+
export class CardPreviewCacheImpl {
|
|
11
|
+
constructor(previewCache) {
|
|
12
|
+
_defineProperty(this, "get", (id, mode) => {
|
|
13
|
+
const cacheKey = getCacheKey(id, mode);
|
|
14
|
+
return this.previewCache.get(cacheKey);
|
|
15
|
+
});
|
|
16
|
+
_defineProperty(this, "set", (id, mode, cardPreview) => {
|
|
17
|
+
const cacheKey = getCacheKey(id, mode);
|
|
18
|
+
this.previewCache.set(cacheKey, cardPreview);
|
|
19
|
+
});
|
|
20
|
+
_defineProperty(this, "remove", (id, mode) => {
|
|
21
|
+
const cacheKey = getCacheKey(id, mode);
|
|
22
|
+
this.previewCache.remove(cacheKey);
|
|
23
|
+
});
|
|
24
|
+
_defineProperty(this, "clear", () => {
|
|
25
|
+
this.previewCache.clear();
|
|
26
|
+
});
|
|
27
|
+
this.previewCache = previewCache;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
export const mediaFilePreviewCache = new CardPreviewCacheImpl(createObjectURLCache());
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { addFileAttrsToUrl } from '@atlaskit/media-client';
|
|
2
|
+
import { SsrPreviewError } from '../errors';
|
|
3
|
+
import { mediaFilePreviewCache } from './cache';
|
|
4
|
+
import { getLocalPreview, getRemotePreview } from './helpers';
|
|
5
|
+
const extendAndCachePreview = (id, mode, preview, mediaBlobUrlAttrs) => {
|
|
6
|
+
let source;
|
|
7
|
+
switch (preview.source) {
|
|
8
|
+
case 'local':
|
|
9
|
+
source = 'cache-local';
|
|
10
|
+
break;
|
|
11
|
+
case 'remote':
|
|
12
|
+
source = 'cache-remote';
|
|
13
|
+
break;
|
|
14
|
+
case 'ssr-server':
|
|
15
|
+
source = 'cache-ssr-server';
|
|
16
|
+
break;
|
|
17
|
+
case 'ssr-client':
|
|
18
|
+
source = 'cache-ssr-client';
|
|
19
|
+
break;
|
|
20
|
+
default:
|
|
21
|
+
source = preview.source;
|
|
22
|
+
}
|
|
23
|
+
// We want to embed some meta context into dataURI for Copy/Paste to work.
|
|
24
|
+
const dataURI = mediaBlobUrlAttrs ? addFileAttrsToUrl(preview.dataURI, mediaBlobUrlAttrs) : preview.dataURI;
|
|
25
|
+
// We store new cardPreview into cache
|
|
26
|
+
mediaFilePreviewCache.set(id, mode, {
|
|
27
|
+
...preview,
|
|
28
|
+
source,
|
|
29
|
+
dataURI
|
|
30
|
+
});
|
|
31
|
+
return {
|
|
32
|
+
...preview,
|
|
33
|
+
dataURI
|
|
34
|
+
};
|
|
35
|
+
};
|
|
36
|
+
export const getSSRPreview = (ssr, mediaClient, id, params, mediaBlobUrlAttrs) => {
|
|
37
|
+
let dataURI;
|
|
38
|
+
try {
|
|
39
|
+
const rawDataURI = mediaClient.getImageUrlSync(id, params);
|
|
40
|
+
// We want to embed some meta context into dataURI for Copy/Paste to work.
|
|
41
|
+
dataURI = mediaBlobUrlAttrs ? addFileAttrsToUrl(rawDataURI, mediaBlobUrlAttrs) : rawDataURI;
|
|
42
|
+
const source = ssr === 'client' ? 'ssr-client' : 'ssr-server';
|
|
43
|
+
return {
|
|
44
|
+
dataURI,
|
|
45
|
+
source,
|
|
46
|
+
orientation: 1
|
|
47
|
+
};
|
|
48
|
+
} catch (e) {
|
|
49
|
+
const reason = ssr === 'server' ? 'ssr-server-uri' : 'ssr-client-uri';
|
|
50
|
+
throw new SsrPreviewError(reason, e instanceof Error ? e : undefined);
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
export const isLocalPreview = preview => {
|
|
54
|
+
const localSources = ['local', 'cache-local'];
|
|
55
|
+
return localSources.includes(preview.source);
|
|
56
|
+
};
|
|
57
|
+
export const isSSRClientPreview = preview => {
|
|
58
|
+
const ssrClientSources = ['ssr-client', 'cache-ssr-client'];
|
|
59
|
+
return ssrClientSources.includes(preview.source);
|
|
60
|
+
};
|
|
61
|
+
export const isSSRDataPreview = preview => preview.source === 'ssr-data';
|
|
62
|
+
export const getAndCacheRemotePreview = async (mediaClient, id, dimensions, params, mediaBlobUrlAttrs, traceContext) => {
|
|
63
|
+
const remotePreview = await getRemotePreview(mediaClient, id, params, traceContext);
|
|
64
|
+
return extendAndCachePreview(id, params.mode, {
|
|
65
|
+
...remotePreview,
|
|
66
|
+
dimensions
|
|
67
|
+
}, mediaBlobUrlAttrs);
|
|
68
|
+
};
|
|
69
|
+
export const getAndCacheLocalPreview = async (id, filePreview, dimensions, mode, mediaBlobUrlAttrs) => {
|
|
70
|
+
const localPreview = await getLocalPreview(filePreview);
|
|
71
|
+
return extendAndCachePreview(id, mode, {
|
|
72
|
+
...localPreview,
|
|
73
|
+
dimensions
|
|
74
|
+
}, mediaBlobUrlAttrs);
|
|
75
|
+
};
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { getMediaTypeFromMimeType } from '@atlaskit/media-common';
|
|
2
|
+
import { getOrientation } from '@atlaskit/media-ui';
|
|
3
|
+
import { LocalPreviewError, RemotePreviewError } from '../errors';
|
|
4
|
+
import { takeSnapshot } from './videoSnapshot';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* This method tells the support for the media
|
|
8
|
+
* types covered in getCardPreviewFromFilePreview
|
|
9
|
+
*/
|
|
10
|
+
export const isSupportedLocalPreview = mediaType => mediaType === 'image' || mediaType === 'video';
|
|
11
|
+
const getImageLocalPreview = async value => {
|
|
12
|
+
try {
|
|
13
|
+
const orientation = await getOrientation(value);
|
|
14
|
+
const dataURI = URL.createObjectURL(value);
|
|
15
|
+
return {
|
|
16
|
+
dataURI,
|
|
17
|
+
orientation,
|
|
18
|
+
source: 'local'
|
|
19
|
+
};
|
|
20
|
+
} catch (e) {
|
|
21
|
+
throw new LocalPreviewError('local-preview-image', e instanceof Error ? e : undefined);
|
|
22
|
+
}
|
|
23
|
+
};
|
|
24
|
+
const getVideoLocalPreview = async value => {
|
|
25
|
+
try {
|
|
26
|
+
const dataURI = await takeSnapshot(value);
|
|
27
|
+
return {
|
|
28
|
+
dataURI,
|
|
29
|
+
orientation: 1,
|
|
30
|
+
source: 'local'
|
|
31
|
+
};
|
|
32
|
+
} catch (e) {
|
|
33
|
+
throw new LocalPreviewError('local-preview-video', e instanceof Error ? e : undefined);
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
export const getLocalPreview = async filePreview => {
|
|
37
|
+
let value;
|
|
38
|
+
try {
|
|
39
|
+
const resolvedFilePreview = await filePreview;
|
|
40
|
+
value = resolvedFilePreview.value;
|
|
41
|
+
} catch (e) {
|
|
42
|
+
throw new LocalPreviewError('local-preview-rejected', e instanceof Error ? e : undefined);
|
|
43
|
+
}
|
|
44
|
+
if (typeof value === 'string') {
|
|
45
|
+
return {
|
|
46
|
+
dataURI: value,
|
|
47
|
+
orientation: 1,
|
|
48
|
+
source: 'local'
|
|
49
|
+
};
|
|
50
|
+
} else if (value instanceof Blob) {
|
|
51
|
+
const {
|
|
52
|
+
type
|
|
53
|
+
} = value;
|
|
54
|
+
const mediaType = getMediaTypeFromMimeType(type);
|
|
55
|
+
switch (mediaType) {
|
|
56
|
+
case 'image':
|
|
57
|
+
return getImageLocalPreview(value);
|
|
58
|
+
case 'video':
|
|
59
|
+
return getVideoLocalPreview(value);
|
|
60
|
+
default:
|
|
61
|
+
throw new LocalPreviewError('local-preview-unsupported');
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
throw new LocalPreviewError('local-preview-unsupported');
|
|
65
|
+
};
|
|
66
|
+
export const getRemotePreview = async (mediaClient, id, params, traceContext) => {
|
|
67
|
+
try {
|
|
68
|
+
const blob = await mediaClient.getImage(id, params, undefined, undefined, traceContext);
|
|
69
|
+
return {
|
|
70
|
+
dataURI: URL.createObjectURL(blob),
|
|
71
|
+
orientation: 1,
|
|
72
|
+
source: 'remote'
|
|
73
|
+
};
|
|
74
|
+
} catch (e) {
|
|
75
|
+
throw new RemotePreviewError('remote-preview-fetch', e instanceof Error ? e : undefined);
|
|
76
|
+
}
|
|
77
|
+
};
|