@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.
Files changed (121) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/README.md +1 -0
  3. package/dist/fetch.d.ts +5 -1
  4. package/dist/fetch.d.ts.map +1 -1
  5. package/dist/fetch.js +5 -9
  6. package/dist/fetch.js.map +1 -1
  7. package/dist/fetchSnapshot.d.ts +1 -1
  8. package/dist/fetchSnapshot.d.ts.map +1 -1
  9. package/dist/fetchSnapshot.js +8 -5
  10. package/dist/fetchSnapshot.js.map +1 -1
  11. package/dist/getFileLink.d.ts +1 -1
  12. package/dist/getFileLink.d.ts.map +1 -1
  13. package/dist/getFileLink.js +3 -3
  14. package/dist/getFileLink.js.map +1 -1
  15. package/dist/index.d.ts +0 -1
  16. package/dist/index.d.ts.map +1 -1
  17. package/dist/index.js +1 -3
  18. package/dist/index.js.map +1 -1
  19. package/dist/mockify.d.ts +60 -0
  20. package/dist/mockify.d.ts.map +1 -0
  21. package/dist/mockify.js +61 -0
  22. package/dist/mockify.js.map +1 -0
  23. package/dist/odspDelayLoadedDeltaStream.js +2 -2
  24. package/dist/odspDelayLoadedDeltaStream.js.map +1 -1
  25. package/dist/odspDocumentDeltaConnection.js +3 -3
  26. package/dist/odspDocumentDeltaConnection.js.map +1 -1
  27. package/dist/odspDocumentStorageManager.js +6 -6
  28. package/dist/odspDocumentStorageManager.js.map +1 -1
  29. package/dist/odspUtils.d.ts +3 -0
  30. package/dist/odspUtils.d.ts.map +1 -1
  31. package/dist/odspUtils.js +9 -9
  32. package/dist/odspUtils.js.map +1 -1
  33. package/dist/opsCaching.js +2 -2
  34. package/dist/opsCaching.js.map +1 -1
  35. package/dist/packageVersion.d.ts +1 -1
  36. package/dist/packageVersion.js +1 -1
  37. package/dist/packageVersion.js.map +1 -1
  38. package/dist/prefetchLatestSnapshot.js +1 -1
  39. package/dist/prefetchLatestSnapshot.js.map +1 -1
  40. package/dist/retryUtils.js +4 -4
  41. package/dist/retryUtils.js.map +1 -1
  42. package/dist/socketModule.d.ts +2 -1
  43. package/dist/socketModule.d.ts.map +1 -1
  44. package/dist/socketModule.js +2 -3
  45. package/dist/socketModule.js.map +1 -1
  46. package/dist/vroom.d.ts +1 -1
  47. package/dist/vroom.d.ts.map +1 -1
  48. package/dist/vroom.js +3 -3
  49. package/dist/vroom.js.map +1 -1
  50. package/lib/fetch.d.ts +5 -1
  51. package/lib/fetch.d.ts.map +1 -1
  52. package/lib/fetch.js +5 -5
  53. package/lib/fetch.js.map +1 -1
  54. package/lib/fetchSnapshot.d.ts +1 -1
  55. package/lib/fetchSnapshot.d.ts.map +1 -1
  56. package/lib/fetchSnapshot.js +8 -4
  57. package/lib/fetchSnapshot.js.map +1 -1
  58. package/lib/getFileLink.d.ts +1 -1
  59. package/lib/getFileLink.d.ts.map +1 -1
  60. package/lib/getFileLink.js +3 -2
  61. package/lib/getFileLink.js.map +1 -1
  62. package/lib/index.d.ts +0 -1
  63. package/lib/index.d.ts.map +1 -1
  64. package/lib/index.js +0 -1
  65. package/lib/index.js.map +1 -1
  66. package/lib/mockify.d.ts +60 -0
  67. package/lib/mockify.d.ts.map +1 -0
  68. package/lib/mockify.js +57 -0
  69. package/lib/mockify.js.map +1 -0
  70. package/lib/odspDelayLoadedDeltaStream.js +3 -3
  71. package/lib/odspDelayLoadedDeltaStream.js.map +1 -1
  72. package/lib/odspDocumentDeltaConnection.js +4 -4
  73. package/lib/odspDocumentDeltaConnection.js.map +1 -1
  74. package/lib/odspDocumentStorageManager.js +7 -7
  75. package/lib/odspDocumentStorageManager.js.map +1 -1
  76. package/lib/odspUtils.d.ts +3 -0
  77. package/lib/odspUtils.d.ts.map +1 -1
  78. package/lib/odspUtils.js +9 -9
  79. package/lib/odspUtils.js.map +1 -1
  80. package/lib/opsCaching.js +3 -3
  81. package/lib/opsCaching.js.map +1 -1
  82. package/lib/packageVersion.d.ts +1 -1
  83. package/lib/packageVersion.js +1 -1
  84. package/lib/packageVersion.js.map +1 -1
  85. package/lib/prefetchLatestSnapshot.js +2 -2
  86. package/lib/prefetchLatestSnapshot.js.map +1 -1
  87. package/lib/retryUtils.js +5 -5
  88. package/lib/retryUtils.js.map +1 -1
  89. package/lib/socketModule.d.ts +2 -1
  90. package/lib/socketModule.d.ts.map +1 -1
  91. package/lib/socketModule.js +2 -3
  92. package/lib/socketModule.js.map +1 -1
  93. package/lib/vroom.d.ts +1 -1
  94. package/lib/vroom.d.ts.map +1 -1
  95. package/lib/vroom.js +3 -2
  96. package/lib/vroom.js.map +1 -1
  97. package/package.json +17 -19
  98. package/src/fetch.ts +5 -11
  99. package/src/fetchSnapshot.ts +79 -73
  100. package/src/getFileLink.ts +56 -53
  101. package/src/index.ts +0 -1
  102. package/src/mockify.ts +67 -0
  103. package/src/odspDelayLoadedDeltaStream.ts +3 -3
  104. package/src/odspDocumentDeltaConnection.ts +4 -4
  105. package/src/odspDocumentStorageManager.ts +7 -7
  106. package/src/odspUtils.ts +10 -10
  107. package/src/opsCaching.ts +3 -3
  108. package/src/packageVersion.ts +1 -1
  109. package/src/prefetchLatestSnapshot.ts +2 -2
  110. package/src/retryUtils.ts +5 -5
  111. package/src/socketModule.ts +3 -3
  112. package/src/vroom.ts +92 -89
  113. package/dist/odspDocumentServiceFactoryWithCodeSplit.d.ts +0 -16
  114. package/dist/odspDocumentServiceFactoryWithCodeSplit.d.ts.map +0 -1
  115. package/dist/odspDocumentServiceFactoryWithCodeSplit.js +0 -20
  116. package/dist/odspDocumentServiceFactoryWithCodeSplit.js.map +0 -1
  117. package/lib/odspDocumentServiceFactoryWithCodeSplit.d.ts +0 -16
  118. package/lib/odspDocumentServiceFactoryWithCodeSplit.d.ts.map +0 -1
  119. package/lib/odspDocumentServiceFactoryWithCodeSplit.js +0 -16
  120. package/lib/odspDocumentServiceFactoryWithCodeSplit.js.map +0 -1
  121. package/src/odspDocumentServiceFactoryWithCodeSplit.ts +0 -33
@@ -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 async function downloadSnapshot(
697
- odspResolvedUrl: IOdspResolvedUrl,
698
- getAuthHeader: InstrumentedStorageTokenFetcher,
699
- tokenFetchOptions: TokenFetchOptionsEx,
700
- loadingGroupIds: string[] | undefined,
701
- snapshotOptions: ISnapshotOptions | undefined,
702
- snapshotFormatFetchType?: SnapshotFormatSupportType,
703
- controller?: AbortController,
704
- epochTracker?: EpochTracker,
705
- scenarioName?: string,
706
- ): Promise<ISnapshotRequestAndResponseOptions> {
707
- // back-compat: This block to be removed with #8784 when we only consume/consider odsp resolvers that are >= 0.51
708
- // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-explicit-any
709
- const sharingLinkToRedeem = (odspResolvedUrl as any).sharingLinkToRedeem;
710
- if (sharingLinkToRedeem) {
711
- // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
712
- odspResolvedUrl.shareLinkInfo = { ...odspResolvedUrl.shareLinkInfo, sharingLinkToRedeem };
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
- const snapshotUrl = odspResolvedUrl.endpoints.snapshotStorageUrl;
720
+ const snapshotUrl = odspResolvedUrl.endpoints.snapshotStorageUrl;
716
721
 
717
- const queryParams: Record<string, unknown> = { ump: 1 };
718
- if (snapshotOptions !== undefined) {
719
- for (const [key, value] of Object.entries(snapshotOptions)) {
720
- // Exclude "timeout" from query string
721
- if (value !== undefined && key !== "timeout") {
722
- queryParams[key] = value;
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
- if (loadingGroupIds !== undefined) {
728
- queryParams.groupId = loadingGroupIds.join(",");
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
- default: {
758
- // By default ask both versions and let the server decide the format.
759
- headers.accept = `application/json, application/ms-fluid; v=${currentReadVersion}`;
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
- const odspResponse = await (epochTracker?.fetch(
764
- url,
765
- fetchOptions,
766
- "treesLatest",
767
- true,
768
- scenarioName,
769
- ) ?? fetchHelper(url, fetchOptions));
770
-
771
- return {
772
- odspResponse,
773
- requestHeaders: headers,
774
- requestUrl: url,
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,
@@ -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 async function getFileLink(
46
- getToken: TokenFetcher<OdspResourceTokenFetchOptions>,
47
- resolvedUrl: IOdspResolvedUrl,
48
- logger: ITelemetryLoggerExt,
49
- ): Promise<string> {
50
- const cacheKey = `${resolvedUrl.siteUrl}_${resolvedUrl.driveId}_${resolvedUrl.itemId}`;
51
- const maybeFileLinkCacheEntry = fileLinkCache.get(cacheKey);
52
- if (maybeFileLinkCacheEntry !== undefined) {
53
- return maybeFileLinkCacheEntry;
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
- const fileLinkGenerator = async function (): Promise<string> {
57
- let fileLinkCore: string;
58
- try {
59
- let retryCount = 0;
60
- fileLinkCore = await runWithRetry(
61
- async () =>
62
- runWithRetryForCoherencyAndServiceReadOnlyErrors(
63
- async () =>
64
- getFileLinkWithLocationRedirectionHandling(getToken, resolvedUrl, logger),
65
- "getFileLinkCore",
66
- logger,
67
- ),
68
- "getShareLink",
69
- logger,
70
- {
71
- // TODO: use a stronger type
72
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
73
- onRetry(delayInMs: number, error: any) {
74
- retryCount++;
75
- if (retryCount === 5) {
76
- if (error !== undefined && typeof error === "object") {
77
- // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
78
- error.canRetry = false;
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
- throw error;
82
- }
85
+ },
83
86
  },
84
- },
85
- );
86
- } catch (error) {
87
- // Delete from the cache to permit retrying later.
88
- fileLinkCache.delete(cacheKey);
89
- throw error;
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
- // We are guaranteed to run the getFileLinkCore at least once with successful result (which must be a string)
93
- assert(
94
- fileLinkCore !== undefined,
95
- 0x292 /* "Unexpected undefined result from getFileLinkCore" */,
96
- );
97
- return fileLinkCore;
98
- };
99
- const fileLink = fileLinkGenerator();
100
- fileLinkCache.set(cacheKey, fileLink);
101
- return fileLink;
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 { performance } from "@fluid-internal/client-utils";
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 = performance.now();
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 = performance.now() - startTime;
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, performance } from "@fluid-internal/client-utils";
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 = performance.now();
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: performance.now() - payloadToDelete.start,
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 : performance.now() - data.start,
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 { performance } from "@fluid-internal/client-utils";
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 = performance.now();
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 = performance.now();
397
+ const startTime = performanceNow();
398
398
  retrievedSnapshot = await cachedSnapshotP;
399
- cacheLookupTimeInSerialFetch = performance.now() - startTime;
399
+ cacheLookupTimeInSerialFetch = performanceNow() - startTime;
400
400
  method = retrievedSnapshot === undefined ? "network" : "cache";
401
401
 
402
402
  if (retrievedSnapshot === undefined) {
403
- prefetchWaitStartTime = performance.now();
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 = performance.now();
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: performance.now() - stTime,
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 { performance } from "@fluid-internal/client-utils";
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 = performance.now();
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 as unknown as Response;
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: performance.now() - start,
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 = performance.now();
511
+ const start = performanceNow();
512
512
  const result = callback();
513
- const time = performance.now() - start;
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 = performance.now();
518
+ const start = performanceNow();
519
519
  const result = await callback();
520
- const time = performance.now() - start;
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 { performance } from "@fluid-internal/client-utils";
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 = performance.now();
186
+ const start = performanceNow();
187
187
 
188
188
  const messages = await this.getCore(from, to);
189
189
 
190
- const duration = performance.now() - start;
190
+ const duration = performanceNow() - start;
191
191
  if (messages.length > 0 || duration > 1000) {
192
192
  this.logger.sendPerformanceEvent({
193
193
  eventName: "CacheOpsUsed",
@@ -6,4 +6,4 @@
6
6
  */
7
7
 
8
8
  export const pkgName = "@fluidframework/odsp-driver";
9
- export const pkgVersion = "2.20.0";
9
+ export const pkgVersion = "2.22.0";
@@ -3,7 +3,7 @@
3
3
  * Licensed under the MIT License.
4
4
  */
5
5
 
6
- import { performance } from "@fluid-internal/client-utils";
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 = performance.now();
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 { performance } from "@fluid-internal/client-utils";
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 = performance.now();
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: performance.now() - start,
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: performance.now() - start, // record total wait time.
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: performance.now() - start, // record total wait time.
89
+ duration: performanceNow() - start, // record total wait time.
90
90
  },
91
91
  error,
92
92
  );