@fluidframework/odsp-driver 2.20.0 → 2.22.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/CHANGELOG.md +16 -0
- package/README.md +1 -0
- package/dist/fetch.d.ts +5 -1
- package/dist/fetch.d.ts.map +1 -1
- package/dist/fetch.js +5 -9
- package/dist/fetch.js.map +1 -1
- package/dist/fetchSnapshot.d.ts +1 -1
- package/dist/fetchSnapshot.d.ts.map +1 -1
- package/dist/fetchSnapshot.js +8 -5
- package/dist/fetchSnapshot.js.map +1 -1
- package/dist/getFileLink.d.ts +1 -1
- package/dist/getFileLink.d.ts.map +1 -1
- package/dist/getFileLink.js +3 -3
- package/dist/getFileLink.js.map +1 -1
- package/dist/index.d.ts +0 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -3
- package/dist/index.js.map +1 -1
- package/dist/mockify.d.ts +60 -0
- package/dist/mockify.d.ts.map +1 -0
- package/dist/mockify.js +61 -0
- package/dist/mockify.js.map +1 -0
- package/dist/odspDelayLoadedDeltaStream.js +2 -2
- package/dist/odspDelayLoadedDeltaStream.js.map +1 -1
- package/dist/odspDocumentDeltaConnection.js +3 -3
- package/dist/odspDocumentDeltaConnection.js.map +1 -1
- package/dist/odspDocumentStorageManager.js +6 -6
- package/dist/odspDocumentStorageManager.js.map +1 -1
- package/dist/odspUtils.d.ts +3 -0
- package/dist/odspUtils.d.ts.map +1 -1
- package/dist/odspUtils.js +9 -9
- package/dist/odspUtils.js.map +1 -1
- package/dist/opsCaching.js +2 -2
- package/dist/opsCaching.js.map +1 -1
- package/dist/packageVersion.d.ts +1 -1
- package/dist/packageVersion.js +1 -1
- package/dist/packageVersion.js.map +1 -1
- package/dist/prefetchLatestSnapshot.js +1 -1
- package/dist/prefetchLatestSnapshot.js.map +1 -1
- package/dist/retryUtils.js +4 -4
- package/dist/retryUtils.js.map +1 -1
- package/dist/socketModule.d.ts +2 -1
- package/dist/socketModule.d.ts.map +1 -1
- package/dist/socketModule.js +2 -3
- package/dist/socketModule.js.map +1 -1
- package/dist/vroom.d.ts +1 -1
- package/dist/vroom.d.ts.map +1 -1
- package/dist/vroom.js +3 -3
- package/dist/vroom.js.map +1 -1
- package/lib/fetch.d.ts +5 -1
- package/lib/fetch.d.ts.map +1 -1
- package/lib/fetch.js +5 -5
- package/lib/fetch.js.map +1 -1
- package/lib/fetchSnapshot.d.ts +1 -1
- package/lib/fetchSnapshot.d.ts.map +1 -1
- package/lib/fetchSnapshot.js +8 -4
- package/lib/fetchSnapshot.js.map +1 -1
- package/lib/getFileLink.d.ts +1 -1
- package/lib/getFileLink.d.ts.map +1 -1
- package/lib/getFileLink.js +3 -2
- package/lib/getFileLink.js.map +1 -1
- package/lib/index.d.ts +0 -1
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +0 -1
- package/lib/index.js.map +1 -1
- package/lib/mockify.d.ts +60 -0
- package/lib/mockify.d.ts.map +1 -0
- package/lib/mockify.js +57 -0
- package/lib/mockify.js.map +1 -0
- package/lib/odspDelayLoadedDeltaStream.js +3 -3
- package/lib/odspDelayLoadedDeltaStream.js.map +1 -1
- package/lib/odspDocumentDeltaConnection.js +4 -4
- package/lib/odspDocumentDeltaConnection.js.map +1 -1
- package/lib/odspDocumentStorageManager.js +7 -7
- package/lib/odspDocumentStorageManager.js.map +1 -1
- package/lib/odspUtils.d.ts +3 -0
- package/lib/odspUtils.d.ts.map +1 -1
- package/lib/odspUtils.js +9 -9
- package/lib/odspUtils.js.map +1 -1
- package/lib/opsCaching.js +3 -3
- package/lib/opsCaching.js.map +1 -1
- package/lib/packageVersion.d.ts +1 -1
- package/lib/packageVersion.js +1 -1
- package/lib/packageVersion.js.map +1 -1
- package/lib/prefetchLatestSnapshot.js +2 -2
- package/lib/prefetchLatestSnapshot.js.map +1 -1
- package/lib/retryUtils.js +5 -5
- package/lib/retryUtils.js.map +1 -1
- package/lib/socketModule.d.ts +2 -1
- package/lib/socketModule.d.ts.map +1 -1
- package/lib/socketModule.js +2 -3
- package/lib/socketModule.js.map +1 -1
- package/lib/vroom.d.ts +1 -1
- package/lib/vroom.d.ts.map +1 -1
- package/lib/vroom.js +3 -2
- package/lib/vroom.js.map +1 -1
- package/package.json +17 -19
- package/src/fetch.ts +5 -11
- package/src/fetchSnapshot.ts +79 -73
- package/src/getFileLink.ts +56 -53
- package/src/index.ts +0 -1
- package/src/mockify.ts +67 -0
- package/src/odspDelayLoadedDeltaStream.ts +3 -3
- package/src/odspDocumentDeltaConnection.ts +4 -4
- package/src/odspDocumentStorageManager.ts +7 -7
- package/src/odspUtils.ts +10 -10
- package/src/opsCaching.ts +3 -3
- package/src/packageVersion.ts +1 -1
- package/src/prefetchLatestSnapshot.ts +2 -2
- package/src/retryUtils.ts +5 -5
- package/src/socketModule.ts +3 -3
- package/src/vroom.ts +92 -89
- package/dist/odspDocumentServiceFactoryWithCodeSplit.d.ts +0 -16
- package/dist/odspDocumentServiceFactoryWithCodeSplit.d.ts.map +0 -1
- package/dist/odspDocumentServiceFactoryWithCodeSplit.js +0 -20
- package/dist/odspDocumentServiceFactoryWithCodeSplit.js.map +0 -1
- package/lib/odspDocumentServiceFactoryWithCodeSplit.d.ts +0 -16
- package/lib/odspDocumentServiceFactoryWithCodeSplit.d.ts.map +0 -1
- package/lib/odspDocumentServiceFactoryWithCodeSplit.js +0 -16
- package/lib/odspDocumentServiceFactoryWithCodeSplit.js.map +0 -1
- package/src/odspDocumentServiceFactoryWithCodeSplit.ts +0 -33
package/src/fetchSnapshot.ts
CHANGED
|
@@ -46,6 +46,7 @@ import {
|
|
|
46
46
|
import { EpochTracker } from "./epochTracker.js";
|
|
47
47
|
import { getQueryString } from "./getQueryString.js";
|
|
48
48
|
import { getHeadersWithAuth } from "./getUrlAndHeadersWithAuth.js";
|
|
49
|
+
import { mockify } from "./mockify.js";
|
|
49
50
|
import { convertOdspSnapshotToSnapshotTreeAndBlobs } from "./odspSnapshotParser.js";
|
|
50
51
|
import { checkForKnownServerFarmType } from "./odspUrlHelper.js";
|
|
51
52
|
import {
|
|
@@ -693,87 +694,92 @@ function getTreeStatsCore(snapshotTree: ISnapshotTree, stats: ITreeStats): void
|
|
|
693
694
|
* @param epochTracker - epoch tracker used to add/validate epoch in the network call.
|
|
694
695
|
* @returns fetched snapshot.
|
|
695
696
|
*/
|
|
696
|
-
export
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
697
|
+
export const downloadSnapshot = mockify(
|
|
698
|
+
async (
|
|
699
|
+
odspResolvedUrl: IOdspResolvedUrl,
|
|
700
|
+
getAuthHeader: InstrumentedStorageTokenFetcher,
|
|
701
|
+
tokenFetchOptions: TokenFetchOptionsEx,
|
|
702
|
+
loadingGroupIds: string[] | undefined,
|
|
703
|
+
snapshotOptions: ISnapshotOptions | undefined,
|
|
704
|
+
snapshotFormatFetchType?: SnapshotFormatSupportType,
|
|
705
|
+
controller?: AbortController,
|
|
706
|
+
epochTracker?: EpochTracker,
|
|
707
|
+
scenarioName?: string,
|
|
708
|
+
): Promise<ISnapshotRequestAndResponseOptions> => {
|
|
709
|
+
// back-compat: This block to be removed with #8784 when we only consume/consider odsp resolvers that are >= 0.51
|
|
710
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-explicit-any
|
|
711
|
+
const sharingLinkToRedeem = (odspResolvedUrl as any).sharingLinkToRedeem;
|
|
712
|
+
if (sharingLinkToRedeem) {
|
|
713
|
+
odspResolvedUrl.shareLinkInfo = {
|
|
714
|
+
...odspResolvedUrl.shareLinkInfo,
|
|
715
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
|
716
|
+
sharingLinkToRedeem,
|
|
717
|
+
};
|
|
718
|
+
}
|
|
714
719
|
|
|
715
|
-
|
|
720
|
+
const snapshotUrl = odspResolvedUrl.endpoints.snapshotStorageUrl;
|
|
716
721
|
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
722
|
+
const queryParams: Record<string, unknown> = { ump: 1 };
|
|
723
|
+
if (snapshotOptions !== undefined) {
|
|
724
|
+
for (const [key, value] of Object.entries(snapshotOptions)) {
|
|
725
|
+
// Exclude "timeout" from query string
|
|
726
|
+
if (value !== undefined && key !== "timeout") {
|
|
727
|
+
queryParams[key] = value;
|
|
728
|
+
}
|
|
723
729
|
}
|
|
724
730
|
}
|
|
725
|
-
}
|
|
726
731
|
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
}
|
|
730
|
-
|
|
731
|
-
const queryString = getQueryString(queryParams);
|
|
732
|
-
const url = `${snapshotUrl}/trees/latest${queryString}`;
|
|
733
|
-
const method = "POST";
|
|
734
|
-
// The location of file can move on Spo in which case server returns 308(Permanent Redirect) error.
|
|
735
|
-
// Adding below header will make VROOM API return 404 instead of 308 and browser can intercept it.
|
|
736
|
-
// This error thrown by server will contain the new redirect location. Look at the 404 error parsing
|
|
737
|
-
// for further reference here: \packages\utils\odsp-doclib-utils\src\odspErrorUtils.ts
|
|
738
|
-
const header = { prefer: "manualredirect" };
|
|
739
|
-
const authHeader = await getAuthHeader(
|
|
740
|
-
{ ...tokenFetchOptions, request: { url, method } },
|
|
741
|
-
"downloadSnapshot",
|
|
742
|
-
);
|
|
743
|
-
assert(authHeader !== null, 0x1e5 /* "Storage token should not be null" */);
|
|
744
|
-
const { body, headers } = getFormBodyAndHeaders(odspResolvedUrl, authHeader, header);
|
|
745
|
-
const fetchOptions = {
|
|
746
|
-
body,
|
|
747
|
-
headers,
|
|
748
|
-
signal: controller?.signal,
|
|
749
|
-
method,
|
|
750
|
-
};
|
|
751
|
-
// Decide what snapshot format to fetch as per the feature gate.
|
|
752
|
-
switch (snapshotFormatFetchType) {
|
|
753
|
-
case SnapshotFormatSupportType.Binary: {
|
|
754
|
-
headers.accept = `application/ms-fluid; v=${currentReadVersion}`;
|
|
755
|
-
break;
|
|
732
|
+
if (loadingGroupIds !== undefined) {
|
|
733
|
+
queryParams.groupId = loadingGroupIds.join(",");
|
|
756
734
|
}
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
735
|
+
|
|
736
|
+
const queryString = getQueryString(queryParams);
|
|
737
|
+
const url = `${snapshotUrl}/trees/latest${queryString}`;
|
|
738
|
+
const method = "POST";
|
|
739
|
+
// The location of file can move on Spo in which case server returns 308(Permanent Redirect) error.
|
|
740
|
+
// Adding below header will make VROOM API return 404 instead of 308 and browser can intercept it.
|
|
741
|
+
// This error thrown by server will contain the new redirect location. Look at the 404 error parsing
|
|
742
|
+
// for further reference here: \packages\utils\odsp-doclib-utils\src\odspErrorUtils.ts
|
|
743
|
+
const header = { prefer: "manualredirect" };
|
|
744
|
+
const authHeader = await getAuthHeader(
|
|
745
|
+
{ ...tokenFetchOptions, request: { url, method } },
|
|
746
|
+
"downloadSnapshot",
|
|
747
|
+
);
|
|
748
|
+
assert(authHeader !== null, 0x1e5 /* "Storage token should not be null" */);
|
|
749
|
+
const { body, headers } = getFormBodyAndHeaders(odspResolvedUrl, authHeader, header);
|
|
750
|
+
const fetchOptions = {
|
|
751
|
+
body,
|
|
752
|
+
headers,
|
|
753
|
+
signal: controller?.signal,
|
|
754
|
+
method,
|
|
755
|
+
};
|
|
756
|
+
// Decide what snapshot format to fetch as per the feature gate.
|
|
757
|
+
switch (snapshotFormatFetchType) {
|
|
758
|
+
case SnapshotFormatSupportType.Binary: {
|
|
759
|
+
headers.accept = `application/ms-fluid; v=${currentReadVersion}`;
|
|
760
|
+
break;
|
|
761
|
+
}
|
|
762
|
+
default: {
|
|
763
|
+
// By default ask both versions and let the server decide the format.
|
|
764
|
+
headers.accept = `application/json, application/ms-fluid; v=${currentReadVersion}`;
|
|
765
|
+
}
|
|
760
766
|
}
|
|
761
|
-
}
|
|
762
767
|
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
}
|
|
768
|
+
const odspResponse = await (epochTracker?.fetch(
|
|
769
|
+
url,
|
|
770
|
+
fetchOptions,
|
|
771
|
+
"treesLatest",
|
|
772
|
+
true,
|
|
773
|
+
scenarioName,
|
|
774
|
+
) ?? fetchHelper(url, fetchOptions));
|
|
775
|
+
|
|
776
|
+
return {
|
|
777
|
+
odspResponse,
|
|
778
|
+
requestHeaders: headers,
|
|
779
|
+
requestUrl: url,
|
|
780
|
+
};
|
|
781
|
+
},
|
|
782
|
+
);
|
|
777
783
|
|
|
778
784
|
function isRedeemSharingLinkError(
|
|
779
785
|
odspResolvedUrl: IOdspResolvedUrl,
|
package/src/getFileLink.ts
CHANGED
|
@@ -20,6 +20,7 @@ import {
|
|
|
20
20
|
} from "@fluidframework/telemetry-utils/internal";
|
|
21
21
|
|
|
22
22
|
import { getHeadersWithAuth } from "./getUrlAndHeadersWithAuth.js";
|
|
23
|
+
import { mockify } from "./mockify.js";
|
|
23
24
|
import {
|
|
24
25
|
fetchHelper,
|
|
25
26
|
getWithRetryForTokenRefresh,
|
|
@@ -42,64 +43,66 @@ const fileLinkCache = new Map<string, Promise<string>>();
|
|
|
42
43
|
* @param logger - used to log results of operation, including any error
|
|
43
44
|
* @returns Promise which resolves to file link url when successful; otherwise, undefined.
|
|
44
45
|
*/
|
|
45
|
-
export
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
46
|
+
export const getFileLink = mockify(
|
|
47
|
+
async (
|
|
48
|
+
getToken: TokenFetcher<OdspResourceTokenFetchOptions>,
|
|
49
|
+
resolvedUrl: IOdspResolvedUrl,
|
|
50
|
+
logger: ITelemetryLoggerExt,
|
|
51
|
+
): Promise<string> => {
|
|
52
|
+
const cacheKey = `${resolvedUrl.siteUrl}_${resolvedUrl.driveId}_${resolvedUrl.itemId}`;
|
|
53
|
+
const maybeFileLinkCacheEntry = fileLinkCache.get(cacheKey);
|
|
54
|
+
if (maybeFileLinkCacheEntry !== undefined) {
|
|
55
|
+
return maybeFileLinkCacheEntry;
|
|
56
|
+
}
|
|
55
57
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
58
|
+
const fileLinkGenerator = async function (): Promise<string> {
|
|
59
|
+
let fileLinkCore: string;
|
|
60
|
+
try {
|
|
61
|
+
let retryCount = 0;
|
|
62
|
+
fileLinkCore = await runWithRetry(
|
|
63
|
+
async () =>
|
|
64
|
+
runWithRetryForCoherencyAndServiceReadOnlyErrors(
|
|
65
|
+
async () =>
|
|
66
|
+
getFileLinkWithLocationRedirectionHandling(getToken, resolvedUrl, logger),
|
|
67
|
+
"getFileLinkCore",
|
|
68
|
+
logger,
|
|
69
|
+
),
|
|
70
|
+
"getShareLink",
|
|
71
|
+
logger,
|
|
72
|
+
{
|
|
73
|
+
// TODO: use a stronger type
|
|
74
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
75
|
+
onRetry(delayInMs: number, error: any) {
|
|
76
|
+
retryCount++;
|
|
77
|
+
if (retryCount === 5) {
|
|
78
|
+
if (error !== undefined && typeof error === "object") {
|
|
79
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
|
|
80
|
+
error.canRetry = false;
|
|
81
|
+
throw error;
|
|
82
|
+
}
|
|
79
83
|
throw error;
|
|
80
84
|
}
|
|
81
|
-
|
|
82
|
-
}
|
|
85
|
+
},
|
|
83
86
|
},
|
|
84
|
-
|
|
85
|
-
)
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
}
|
|
87
|
+
);
|
|
88
|
+
} catch (error) {
|
|
89
|
+
// Delete from the cache to permit retrying later.
|
|
90
|
+
fileLinkCache.delete(cacheKey);
|
|
91
|
+
throw error;
|
|
92
|
+
}
|
|
91
93
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
}
|
|
94
|
+
// We are guaranteed to run the getFileLinkCore at least once with successful result (which must be a string)
|
|
95
|
+
assert(
|
|
96
|
+
fileLinkCore !== undefined,
|
|
97
|
+
0x292 /* "Unexpected undefined result from getFileLinkCore" */,
|
|
98
|
+
);
|
|
99
|
+
return fileLinkCore;
|
|
100
|
+
};
|
|
101
|
+
const fileLink = fileLinkGenerator();
|
|
102
|
+
fileLinkCache.set(cacheKey, fileLink);
|
|
103
|
+
return fileLink;
|
|
104
|
+
},
|
|
105
|
+
);
|
|
103
106
|
|
|
104
107
|
/**
|
|
105
108
|
* Handles location redirection while fulfilling the getFileLink call. We don't want browser to handle
|
package/src/index.ts
CHANGED
|
@@ -29,7 +29,6 @@ export {
|
|
|
29
29
|
OdspDocumentServiceFactory,
|
|
30
30
|
} from "./odspDocumentServiceFactory.js";
|
|
31
31
|
export { OdspDocumentServiceFactoryCore } from "./odspDocumentServiceFactoryCore.js";
|
|
32
|
-
export { OdspDocumentServiceFactoryWithCodeSplit } from "./odspDocumentServiceFactoryWithCodeSplit.js";
|
|
33
32
|
|
|
34
33
|
// File creation
|
|
35
34
|
export { createOdspCreateContainerRequest } from "./createOdspCreateContainerRequest.js";
|
package/src/mockify.ts
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* Copyright (c) Microsoft Corporation and contributors. All rights reserved.
|
|
3
|
+
* Licensed under the MIT License.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* A special key used to store the original function in a {@link Mockable | mockable} function.
|
|
8
|
+
* @remarks Use {@link mockify | `mockify.key`} as a convenient way to access this key.
|
|
9
|
+
*/
|
|
10
|
+
export const mockifyMockKey = Symbol("`mockify` mock function key");
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* A function that can be mocked after being decorated by {@link mockify | mockify()}.
|
|
14
|
+
*/
|
|
15
|
+
export interface Mockable<T extends (...args: any[]) => unknown> {
|
|
16
|
+
(...args: Parameters<T>): ReturnType<T>;
|
|
17
|
+
[mockifyMockKey]: T;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Decorates a function to allow it to be mocked.
|
|
22
|
+
* @param fn - The function that will become mockable.
|
|
23
|
+
* @returns A function with a {@link mockifyMockKey | special property } that can be overwritten to mock the original function.
|
|
24
|
+
* By default, this property is set to the original function.
|
|
25
|
+
* If overwritten with a new function, the new function will be called instead of the original.
|
|
26
|
+
* @example
|
|
27
|
+
* ```typescript
|
|
28
|
+
* const original = () => console.log("original");
|
|
29
|
+
* const mockable = mockify(original);
|
|
30
|
+
* mockable(); // logs "original"
|
|
31
|
+
* mockable[mockify.key] = () => console.log("mocked");
|
|
32
|
+
* mockable(); // logs "mocked"
|
|
33
|
+
* mockable[mockify.key] = original;
|
|
34
|
+
* mockable(); // logs "original"
|
|
35
|
+
* ```
|
|
36
|
+
*
|
|
37
|
+
* This pattern is useful for mocking top-level exported functions in a module.
|
|
38
|
+
* For example,
|
|
39
|
+
* ```typescript
|
|
40
|
+
* export function fn() { /* ... * / }
|
|
41
|
+
* ```
|
|
42
|
+
* becomes
|
|
43
|
+
* ```typescript
|
|
44
|
+
* import { mockify } from "./mockify.js";
|
|
45
|
+
* export const fn = mockify(() => { /* ... * / });
|
|
46
|
+
* ```
|
|
47
|
+
* and can now be mocked by another module that imports it.
|
|
48
|
+
* ```typescript
|
|
49
|
+
* import * as sinon from "sinon";
|
|
50
|
+
* import { mockify } from "./mockify.js";
|
|
51
|
+
* import { fn } from "./module.js";
|
|
52
|
+
* sinon.stub(fn, mockify.key).callsFake(() => {
|
|
53
|
+
* // ... mock function implementation ...
|
|
54
|
+
* });
|
|
55
|
+
* // ...
|
|
56
|
+
* sinon.restore();
|
|
57
|
+
* ```
|
|
58
|
+
*/
|
|
59
|
+
export function mockify<T extends (...args: any[]) => unknown>(fn: T): Mockable<T> {
|
|
60
|
+
const mockable = (...args: Parameters<T>): ReturnType<T> => {
|
|
61
|
+
return mockable[mockifyMockKey](...args) as ReturnType<T>;
|
|
62
|
+
};
|
|
63
|
+
mockable[mockifyMockKey] = fn;
|
|
64
|
+
return mockable;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
mockify.key = mockifyMockKey;
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* Licensed under the MIT License.
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import {
|
|
6
|
+
import { performanceNow } from "@fluid-internal/client-utils";
|
|
7
7
|
import { ISignalEnvelope } from "@fluidframework/core-interfaces/internal";
|
|
8
8
|
import { assert } from "@fluidframework/core-utils/internal";
|
|
9
9
|
import { IClient } from "@fluidframework/driver-definitions";
|
|
@@ -519,7 +519,7 @@ export class OdspDelayLoadedDeltaStream {
|
|
|
519
519
|
client: IClient,
|
|
520
520
|
webSocketUrl: string,
|
|
521
521
|
): Promise<OdspDocumentDeltaConnection> {
|
|
522
|
-
const startTime =
|
|
522
|
+
const startTime = performanceNow();
|
|
523
523
|
const connection = await OdspDocumentDeltaConnection.create(
|
|
524
524
|
tenantId,
|
|
525
525
|
documentId,
|
|
@@ -531,7 +531,7 @@ export class OdspDelayLoadedDeltaStream {
|
|
|
531
531
|
this.epochTracker,
|
|
532
532
|
this.socketReferenceKeyPrefix,
|
|
533
533
|
);
|
|
534
|
-
const duration =
|
|
534
|
+
const duration = performanceNow() - startTime;
|
|
535
535
|
// This event happens rather often, so it adds up to cost of telemetry.
|
|
536
536
|
// Given that most reconnects result in reusing socket and happen very quickly,
|
|
537
537
|
// report event only if it took longer than threshold.
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* Licensed under the MIT License.
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import { TypedEventEmitter,
|
|
6
|
+
import { TypedEventEmitter, performanceNow } from "@fluid-internal/client-utils";
|
|
7
7
|
import { IEvent } from "@fluidframework/core-interfaces";
|
|
8
8
|
import { assert, Deferred } from "@fluidframework/core-utils/internal";
|
|
9
9
|
import { DocumentDeltaConnection } from "@fluidframework/driver-base/internal";
|
|
@@ -450,7 +450,7 @@ export class OdspDocumentDeltaConnection extends DocumentDeltaConnection {
|
|
|
450
450
|
|
|
451
451
|
this.pushCallCounter++;
|
|
452
452
|
const nonce = `${this.requestOpsNoncePrefix}${this.pushCallCounter}`;
|
|
453
|
-
const start =
|
|
453
|
+
const start = performanceNow();
|
|
454
454
|
|
|
455
455
|
// We may keep keep accumulating memory for nothing, if we are not getting responses.
|
|
456
456
|
// Note that we should not have overlapping requests, as DeltaManager allows only one
|
|
@@ -475,7 +475,7 @@ export class OdspDocumentDeltaConnection extends DocumentDeltaConnection {
|
|
|
475
475
|
from: payloadToDelete.from,
|
|
476
476
|
to: payloadToDelete.to,
|
|
477
477
|
length: payloadToDelete.to - payloadToDelete.from,
|
|
478
|
-
duration:
|
|
478
|
+
duration: performanceNow() - payloadToDelete.start,
|
|
479
479
|
});
|
|
480
480
|
this.getOpsMap.delete(key!);
|
|
481
481
|
}
|
|
@@ -591,7 +591,7 @@ export class OdspDocumentDeltaConnection extends DocumentDeltaConnection {
|
|
|
591
591
|
code: result.code,
|
|
592
592
|
from: data?.from,
|
|
593
593
|
to: data?.to,
|
|
594
|
-
duration: data === undefined ? undefined :
|
|
594
|
+
duration: data === undefined ? undefined : performanceNow() - data.start,
|
|
595
595
|
};
|
|
596
596
|
if (messages !== undefined && messages.length > 0) {
|
|
597
597
|
this.logger.sendPerformanceEvent({
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* Licensed under the MIT License.
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import {
|
|
6
|
+
import { performanceNow } from "@fluid-internal/client-utils";
|
|
7
7
|
import { LogLevel } from "@fluidframework/core-interfaces";
|
|
8
8
|
import { assert, delay } from "@fluidframework/core-utils/internal";
|
|
9
9
|
import { promiseRaceWithWinner } from "@fluidframework/driver-base/internal";
|
|
@@ -271,7 +271,7 @@ export class OdspDocumentStorageService extends OdspDocumentStorageServiceBase {
|
|
|
271
271
|
let retrievedSnapshot: ISnapshot | IPrefetchSnapshotContents | undefined;
|
|
272
272
|
|
|
273
273
|
let method: string;
|
|
274
|
-
let prefetchWaitStartTime: number =
|
|
274
|
+
let prefetchWaitStartTime: number = performanceNow();
|
|
275
275
|
if (snapshotFetchOptions.fetchSource === FetchSource.noCache) {
|
|
276
276
|
retrievedSnapshot = await this.fetchSnapshotFromNetwork(
|
|
277
277
|
hostSnapshotOptions,
|
|
@@ -394,13 +394,13 @@ export class OdspDocumentStorageService extends OdspDocumentStorageServiceBase {
|
|
|
394
394
|
} else {
|
|
395
395
|
// Note: There's a race condition here - another caller may come past the undefined check
|
|
396
396
|
// while the first caller is awaiting later async code in this block.
|
|
397
|
-
const startTime =
|
|
397
|
+
const startTime = performanceNow();
|
|
398
398
|
retrievedSnapshot = await cachedSnapshotP;
|
|
399
|
-
cacheLookupTimeInSerialFetch =
|
|
399
|
+
cacheLookupTimeInSerialFetch = performanceNow() - startTime;
|
|
400
400
|
method = retrievedSnapshot === undefined ? "network" : "cache";
|
|
401
401
|
|
|
402
402
|
if (retrievedSnapshot === undefined) {
|
|
403
|
-
prefetchWaitStartTime =
|
|
403
|
+
prefetchWaitStartTime = performanceNow();
|
|
404
404
|
retrievedSnapshot = await this.fetchSnapshotFromNetwork(
|
|
405
405
|
hostSnapshotOptions,
|
|
406
406
|
snapshotFetchOptions.loadingGroupIds,
|
|
@@ -437,7 +437,7 @@ export class OdspDocumentStorageService extends OdspDocumentStorageServiceBase {
|
|
|
437
437
|
},
|
|
438
438
|
);
|
|
439
439
|
|
|
440
|
-
const stTime =
|
|
440
|
+
const stTime = performanceNow();
|
|
441
441
|
// Don't override ops which were fetched during initial load, since we could still need them.
|
|
442
442
|
const id = this.initializeFromSnapshot(
|
|
443
443
|
odspSnapshotCacheValue,
|
|
@@ -447,7 +447,7 @@ export class OdspDocumentStorageService extends OdspDocumentStorageServiceBase {
|
|
|
447
447
|
this.logger.sendTelemetryEvent(
|
|
448
448
|
{
|
|
449
449
|
eventName: "SnapshotInitializeTime",
|
|
450
|
-
duration:
|
|
450
|
+
duration: performanceNow() - stTime,
|
|
451
451
|
},
|
|
452
452
|
undefined,
|
|
453
453
|
LogLevel.verbose,
|
package/src/odspUtils.ts
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* Licensed under the MIT License.
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import {
|
|
6
|
+
import { performanceNow } from "@fluid-internal/client-utils";
|
|
7
7
|
import {
|
|
8
8
|
ITelemetryBaseLogger,
|
|
9
9
|
ITelemetryBaseProperties,
|
|
@@ -54,7 +54,6 @@ import {
|
|
|
54
54
|
wrapError,
|
|
55
55
|
} from "@fluidframework/telemetry-utils/internal";
|
|
56
56
|
|
|
57
|
-
import { fetch } from "./fetch.js";
|
|
58
57
|
import { storeLocatorInOdspUrl } from "./odspFluidFileLink.js";
|
|
59
58
|
// eslint-disable-next-line import/no-deprecated
|
|
60
59
|
import { ISnapshotContents } from "./odspPublicUtils.js";
|
|
@@ -135,12 +134,11 @@ export async function fetchHelper(
|
|
|
135
134
|
requestInfo: RequestInfo,
|
|
136
135
|
requestInit: RequestInit | undefined,
|
|
137
136
|
): Promise<IOdspResponse<Response>> {
|
|
138
|
-
const start =
|
|
137
|
+
const start = performanceNow();
|
|
139
138
|
|
|
140
|
-
// Node-fetch and dom have conflicting typing, force them to work by casting for now
|
|
141
139
|
return fetch(requestInfo, requestInit).then(
|
|
142
140
|
async (fetchResponse) => {
|
|
143
|
-
const response = fetchResponse
|
|
141
|
+
const response = fetchResponse;
|
|
144
142
|
// Let's assume we can retry.
|
|
145
143
|
if (!response) {
|
|
146
144
|
throw new NonRetryableError(
|
|
@@ -165,7 +163,7 @@ export async function fetchHelper(
|
|
|
165
163
|
content: response,
|
|
166
164
|
headers,
|
|
167
165
|
propsToLog: getSPOAndGraphRequestIdsFromResponse(headers),
|
|
168
|
-
duration:
|
|
166
|
+
duration: performanceNow() - start,
|
|
169
167
|
};
|
|
170
168
|
},
|
|
171
169
|
(error) => {
|
|
@@ -221,6 +219,8 @@ export async function fetchHelper(
|
|
|
221
219
|
},
|
|
222
220
|
);
|
|
223
221
|
}
|
|
222
|
+
// This allows `fetch` to be mocked (e.g. with sinon `stub()`)
|
|
223
|
+
fetchHelper.fetch = fetch;
|
|
224
224
|
|
|
225
225
|
/**
|
|
226
226
|
* A utility function to fetch and parse as JSON with support for retries
|
|
@@ -508,16 +508,16 @@ export function buildOdspShareLinkReqParams(
|
|
|
508
508
|
}
|
|
509
509
|
|
|
510
510
|
export function measure<T>(callback: () => T): [T, number] {
|
|
511
|
-
const start =
|
|
511
|
+
const start = performanceNow();
|
|
512
512
|
const result = callback();
|
|
513
|
-
const time =
|
|
513
|
+
const time = performanceNow() - start;
|
|
514
514
|
return [result, time];
|
|
515
515
|
}
|
|
516
516
|
|
|
517
517
|
export async function measureP<T>(callback: () => Promise<T>): Promise<[T, number]> {
|
|
518
|
-
const start =
|
|
518
|
+
const start = performanceNow();
|
|
519
519
|
const result = await callback();
|
|
520
|
-
const time =
|
|
520
|
+
const time = performanceNow() - start;
|
|
521
521
|
return [result, time];
|
|
522
522
|
}
|
|
523
523
|
|
package/src/opsCaching.ts
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* Licensed under the MIT License.
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import {
|
|
6
|
+
import { performanceNow } from "@fluid-internal/client-utils";
|
|
7
7
|
import { ITelemetryLoggerExt } from "@fluidframework/telemetry-utils/internal";
|
|
8
8
|
|
|
9
9
|
// ISequencedDocumentMessage
|
|
@@ -183,11 +183,11 @@ export class OpsCache {
|
|
|
183
183
|
* @returns ops retrieved
|
|
184
184
|
*/
|
|
185
185
|
public async get(from: number, to?: number): Promise<IMessage[]> {
|
|
186
|
-
const start =
|
|
186
|
+
const start = performanceNow();
|
|
187
187
|
|
|
188
188
|
const messages = await this.getCore(from, to);
|
|
189
189
|
|
|
190
|
-
const duration =
|
|
190
|
+
const duration = performanceNow() - start;
|
|
191
191
|
if (messages.length > 0 || duration > 1000) {
|
|
192
192
|
this.logger.sendPerformanceEvent({
|
|
193
193
|
eventName: "CacheOpsUsed",
|
package/src/packageVersion.ts
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* Licensed under the MIT License.
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import {
|
|
6
|
+
import { performanceNow } from "@fluid-internal/client-utils";
|
|
7
7
|
import { ITelemetryBaseLogger } from "@fluidframework/core-interfaces";
|
|
8
8
|
import { assert, Deferred } from "@fluidframework/core-utils/internal";
|
|
9
9
|
import { IResolvedUrl } from "@fluidframework/driver-definitions/internal";
|
|
@@ -127,7 +127,7 @@ export async function prefetchLatestSnapshot(
|
|
|
127
127
|
odspLogger,
|
|
128
128
|
{ eventName: "PrefetchLatestSnapshot" },
|
|
129
129
|
async () => {
|
|
130
|
-
const prefetchStartTime =
|
|
130
|
+
const prefetchStartTime = performanceNow();
|
|
131
131
|
// Add the deferred promise to the cache, so that it can be leveraged while loading the container.
|
|
132
132
|
const snapshotContentsWithEpochP = new Deferred<IPrefetchSnapshotContents>();
|
|
133
133
|
const nonPersistentCacheKey = getKeyForCacheEntry(snapshotKey);
|
package/src/retryUtils.ts
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* Licensed under the MIT License.
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import {
|
|
6
|
+
import { performanceNow } from "@fluid-internal/client-utils";
|
|
7
7
|
import { delay } from "@fluidframework/core-utils/internal";
|
|
8
8
|
import {
|
|
9
9
|
canRetryOnError,
|
|
@@ -24,7 +24,7 @@ export async function runWithRetry<T>(
|
|
|
24
24
|
checkDisposed?: () => void,
|
|
25
25
|
): Promise<T> {
|
|
26
26
|
let retryAfter = 1000;
|
|
27
|
-
const start =
|
|
27
|
+
const start = performanceNow();
|
|
28
28
|
let lastError: unknown;
|
|
29
29
|
for (let attempts = 1; ; attempts++) {
|
|
30
30
|
if (checkDisposed !== undefined) {
|
|
@@ -38,7 +38,7 @@ export async function runWithRetry<T>(
|
|
|
38
38
|
eventName: "MultipleRetries",
|
|
39
39
|
callName,
|
|
40
40
|
attempts,
|
|
41
|
-
duration:
|
|
41
|
+
duration: performanceNow() - start,
|
|
42
42
|
},
|
|
43
43
|
lastError,
|
|
44
44
|
);
|
|
@@ -62,7 +62,7 @@ export async function runWithRetry<T>(
|
|
|
62
62
|
eventName: `${callName}_firstFailed`,
|
|
63
63
|
callName,
|
|
64
64
|
attempts,
|
|
65
|
-
duration:
|
|
65
|
+
duration: performanceNow() - start, // record total wait time.
|
|
66
66
|
},
|
|
67
67
|
error,
|
|
68
68
|
);
|
|
@@ -86,7 +86,7 @@ export async function runWithRetry<T>(
|
|
|
86
86
|
: "ServiceReadonlyErrorTooManyRetries",
|
|
87
87
|
callName,
|
|
88
88
|
attempts,
|
|
89
|
-
duration:
|
|
89
|
+
duration: performanceNow() - start, // record total wait time.
|
|
90
90
|
},
|
|
91
91
|
error,
|
|
92
92
|
);
|