@fluidframework/odsp-driver 0.59.4001 → 1.1.0-75972

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 (162) hide show
  1. package/.eslintrc.js +1 -1
  2. package/dist/compactSnapshotParser.d.ts +1 -1
  3. package/dist/compactSnapshotParser.d.ts.map +1 -1
  4. package/dist/compactSnapshotParser.js +7 -4
  5. package/dist/compactSnapshotParser.js.map +1 -1
  6. package/dist/compactSnapshotWriter.d.ts +1 -1
  7. package/dist/compactSnapshotWriter.d.ts.map +1 -1
  8. package/dist/compactSnapshotWriter.js +4 -3
  9. package/dist/compactSnapshotWriter.js.map +1 -1
  10. package/dist/contracts.d.ts +1 -1
  11. package/dist/contracts.d.ts.map +1 -1
  12. package/dist/contracts.js.map +1 -1
  13. package/dist/createFile.d.ts.map +1 -1
  14. package/dist/createFile.js.map +1 -1
  15. package/dist/createNewUtils.d.ts +1 -1
  16. package/dist/createNewUtils.d.ts.map +1 -1
  17. package/dist/createNewUtils.js +1 -0
  18. package/dist/createNewUtils.js.map +1 -1
  19. package/dist/epochTracker.d.ts +3 -2
  20. package/dist/epochTracker.d.ts.map +1 -1
  21. package/dist/epochTracker.js +10 -4
  22. package/dist/epochTracker.js.map +1 -1
  23. package/dist/fetchSnapshot.d.ts +7 -6
  24. package/dist/fetchSnapshot.d.ts.map +1 -1
  25. package/dist/fetchSnapshot.js +57 -31
  26. package/dist/fetchSnapshot.js.map +1 -1
  27. package/dist/index.d.ts +2 -0
  28. package/dist/index.d.ts.map +1 -1
  29. package/dist/index.js +3 -0
  30. package/dist/index.js.map +1 -1
  31. package/dist/odspDocumentDeltaConnection.js +18 -17
  32. package/dist/odspDocumentDeltaConnection.js.map +1 -1
  33. package/dist/odspDocumentService.d.ts.map +1 -1
  34. package/dist/odspDocumentService.js +8 -4
  35. package/dist/odspDocumentService.js.map +1 -1
  36. package/dist/odspDocumentServiceFactoryCore.d.ts.map +1 -1
  37. package/dist/odspDocumentServiceFactoryCore.js +2 -2
  38. package/dist/odspDocumentServiceFactoryCore.js.map +1 -1
  39. package/dist/odspDocumentStorageManager.d.ts +2 -3
  40. package/dist/odspDocumentStorageManager.d.ts.map +1 -1
  41. package/dist/odspDocumentStorageManager.js +13 -17
  42. package/dist/odspDocumentStorageManager.js.map +1 -1
  43. package/dist/odspError.d.ts +3 -1
  44. package/dist/odspError.d.ts.map +1 -1
  45. package/dist/odspError.js +14 -5
  46. package/dist/odspError.js.map +1 -1
  47. package/dist/odspPublicUtils.d.ts +14 -0
  48. package/dist/odspPublicUtils.d.ts.map +1 -1
  49. package/dist/odspPublicUtils.js.map +1 -1
  50. package/dist/odspSnapshotParser.d.ts +2 -2
  51. package/dist/odspSnapshotParser.d.ts.map +1 -1
  52. package/dist/odspSnapshotParser.js +7 -4
  53. package/dist/odspSnapshotParser.js.map +1 -1
  54. package/dist/odspSummaryUploadManager.d.ts.map +1 -1
  55. package/dist/odspSummaryUploadManager.js +1 -4
  56. package/dist/odspSummaryUploadManager.js.map +1 -1
  57. package/dist/odspUtils.d.ts +0 -7
  58. package/dist/odspUtils.d.ts.map +1 -1
  59. package/dist/odspUtils.js +4 -3
  60. package/dist/odspUtils.js.map +1 -1
  61. package/dist/packageVersion.d.ts +1 -1
  62. package/dist/packageVersion.d.ts.map +1 -1
  63. package/dist/packageVersion.js +1 -1
  64. package/dist/packageVersion.js.map +1 -1
  65. package/dist/retryErrorsStorageAdapter.d.ts +1 -2
  66. package/dist/retryErrorsStorageAdapter.d.ts.map +1 -1
  67. package/dist/retryErrorsStorageAdapter.js +0 -3
  68. package/dist/retryErrorsStorageAdapter.js.map +1 -1
  69. package/dist/retryUtils.d.ts.map +1 -1
  70. package/dist/retryUtils.js +2 -3
  71. package/dist/retryUtils.js.map +1 -1
  72. package/lib/compactSnapshotParser.d.ts +1 -1
  73. package/lib/compactSnapshotParser.d.ts.map +1 -1
  74. package/lib/compactSnapshotParser.js +7 -4
  75. package/lib/compactSnapshotParser.js.map +1 -1
  76. package/lib/compactSnapshotWriter.d.ts +1 -1
  77. package/lib/compactSnapshotWriter.d.ts.map +1 -1
  78. package/lib/compactSnapshotWriter.js +4 -3
  79. package/lib/compactSnapshotWriter.js.map +1 -1
  80. package/lib/contracts.d.ts +1 -1
  81. package/lib/contracts.d.ts.map +1 -1
  82. package/lib/contracts.js.map +1 -1
  83. package/lib/createFile.d.ts.map +1 -1
  84. package/lib/createFile.js.map +1 -1
  85. package/lib/createNewUtils.d.ts +1 -1
  86. package/lib/createNewUtils.d.ts.map +1 -1
  87. package/lib/createNewUtils.js +1 -0
  88. package/lib/createNewUtils.js.map +1 -1
  89. package/lib/epochTracker.d.ts +3 -2
  90. package/lib/epochTracker.d.ts.map +1 -1
  91. package/lib/epochTracker.js +10 -4
  92. package/lib/epochTracker.js.map +1 -1
  93. package/lib/fetchSnapshot.d.ts +7 -6
  94. package/lib/fetchSnapshot.d.ts.map +1 -1
  95. package/lib/fetchSnapshot.js +58 -33
  96. package/lib/fetchSnapshot.js.map +1 -1
  97. package/lib/index.d.ts +2 -0
  98. package/lib/index.d.ts.map +1 -1
  99. package/lib/index.js +3 -0
  100. package/lib/index.js.map +1 -1
  101. package/lib/odspDocumentDeltaConnection.js +18 -17
  102. package/lib/odspDocumentDeltaConnection.js.map +1 -1
  103. package/lib/odspDocumentService.d.ts.map +1 -1
  104. package/lib/odspDocumentService.js +9 -5
  105. package/lib/odspDocumentService.js.map +1 -1
  106. package/lib/odspDocumentServiceFactoryCore.d.ts.map +1 -1
  107. package/lib/odspDocumentServiceFactoryCore.js +2 -2
  108. package/lib/odspDocumentServiceFactoryCore.js.map +1 -1
  109. package/lib/odspDocumentStorageManager.d.ts +2 -3
  110. package/lib/odspDocumentStorageManager.d.ts.map +1 -1
  111. package/lib/odspDocumentStorageManager.js +13 -17
  112. package/lib/odspDocumentStorageManager.js.map +1 -1
  113. package/lib/odspError.d.ts +3 -1
  114. package/lib/odspError.d.ts.map +1 -1
  115. package/lib/odspError.js +14 -5
  116. package/lib/odspError.js.map +1 -1
  117. package/lib/odspPublicUtils.d.ts +14 -0
  118. package/lib/odspPublicUtils.d.ts.map +1 -1
  119. package/lib/odspPublicUtils.js.map +1 -1
  120. package/lib/odspSnapshotParser.d.ts +2 -2
  121. package/lib/odspSnapshotParser.d.ts.map +1 -1
  122. package/lib/odspSnapshotParser.js +5 -2
  123. package/lib/odspSnapshotParser.js.map +1 -1
  124. package/lib/odspSummaryUploadManager.d.ts.map +1 -1
  125. package/lib/odspSummaryUploadManager.js +1 -4
  126. package/lib/odspSummaryUploadManager.js.map +1 -1
  127. package/lib/odspUtils.d.ts +0 -7
  128. package/lib/odspUtils.d.ts.map +1 -1
  129. package/lib/odspUtils.js +4 -3
  130. package/lib/odspUtils.js.map +1 -1
  131. package/lib/packageVersion.d.ts +1 -1
  132. package/lib/packageVersion.d.ts.map +1 -1
  133. package/lib/packageVersion.js +1 -1
  134. package/lib/packageVersion.js.map +1 -1
  135. package/lib/retryErrorsStorageAdapter.d.ts +1 -2
  136. package/lib/retryErrorsStorageAdapter.d.ts.map +1 -1
  137. package/lib/retryErrorsStorageAdapter.js +0 -3
  138. package/lib/retryErrorsStorageAdapter.js.map +1 -1
  139. package/lib/retryUtils.d.ts.map +1 -1
  140. package/lib/retryUtils.js +3 -4
  141. package/lib/retryUtils.js.map +1 -1
  142. package/package.json +15 -28
  143. package/src/compactSnapshotParser.ts +10 -4
  144. package/src/compactSnapshotWriter.ts +7 -4
  145. package/src/contracts.ts +1 -1
  146. package/src/createFile.ts +2 -2
  147. package/src/createNewUtils.ts +2 -1
  148. package/src/epochTracker.ts +10 -3
  149. package/src/fetchSnapshot.ts +113 -67
  150. package/src/index.ts +4 -0
  151. package/src/odspDocumentDeltaConnection.ts +18 -18
  152. package/src/odspDocumentService.ts +7 -3
  153. package/src/odspDocumentServiceFactoryCore.ts +4 -2
  154. package/src/odspDocumentStorageManager.ts +18 -21
  155. package/src/odspError.ts +23 -10
  156. package/src/odspPublicUtils.ts +17 -0
  157. package/src/odspSnapshotParser.ts +8 -3
  158. package/src/odspSummaryUploadManager.ts +1 -4
  159. package/src/odspUtils.ts +4 -11
  160. package/src/packageVersion.ts +1 -1
  161. package/src/retryErrorsStorageAdapter.ts +0 -8
  162. package/src/retryUtils.ts +3 -4
@@ -55,6 +55,7 @@ export class EpochTracker implements IPersistedFileCache {
55
55
  protected readonly cache: IPersistedCache,
56
56
  protected readonly fileEntry: IFileEntry,
57
57
  protected readonly logger: ITelemetryLogger,
58
+ protected readonly clientIsSummarizer?: boolean,
58
59
  ) {
59
60
  // Limits the max number of concurrent requests to 24.
60
61
  this.rateLimiter = new RateLimiter(24);
@@ -300,7 +301,12 @@ export class EpochTracker implements IPersistedFileCache {
300
301
  }
301
302
 
302
303
  private formatClientCorrelationId(fetchReason?: string) {
303
- const items: string[] = [`driverId=${this.driverId}`, `RequestNumber=${this.networkCallNumber++}`];
304
+ const items: string[] = [
305
+ `driverId=${this.driverId}`,
306
+ `RequestNumber=${this.networkCallNumber++}`,
307
+ `driverVersion=${driverVersion}`,
308
+ `isSummarizer=${this.clientIsSummarizer}`,
309
+ ];
304
310
  if (fetchReason !== undefined) {
305
311
  items.push(`fetchReason=${fetchReason}`);
306
312
  }
@@ -475,8 +481,9 @@ export function createOdspCacheAndTracker(
475
481
  persistedCacheArg: IPersistedCache,
476
482
  nonpersistentCache: INonPersistentCache,
477
483
  fileEntry: IFileEntry,
478
- logger: ITelemetryLogger): ICacheAndTracker {
479
- const epochTracker = new EpochTrackerWithRedemption(persistedCacheArg, fileEntry, logger);
484
+ logger: ITelemetryLogger,
485
+ clientIsSummarizer?: boolean): ICacheAndTracker {
486
+ const epochTracker = new EpochTrackerWithRedemption(persistedCacheArg, fileEntry, logger, clientIsSummarizer);
480
487
  return {
481
488
  cache: {
482
489
  ...nonpersistentCache,
@@ -8,7 +8,7 @@ import { v4 as uuid } from "uuid";
8
8
  import { ITelemetryLogger } from "@fluidframework/common-definitions";
9
9
  import { assert, fromUtf8ToBase64, performance } from "@fluidframework/common-utils";
10
10
  import { DriverErrorType } from "@fluidframework/driver-definitions";
11
- import { PerformanceEvent } from "@fluidframework/telemetry-utils";
11
+ import { isFluidError, PerformanceEvent, wrapError } from "@fluidframework/telemetry-utils";
12
12
  import {
13
13
  IOdspResolvedUrl,
14
14
  ISnapshotOptions,
@@ -16,7 +16,7 @@ import {
16
16
  InstrumentedStorageTokenFetcher,
17
17
  } from "@fluidframework/odsp-driver-definitions";
18
18
  import { ISnapshotTree } from "@fluidframework/protocol-definitions";
19
- import { isRuntimeMessage } from "@fluidframework/driver-utils";
19
+ import { isRuntimeMessage, NonRetryableError } from "@fluidframework/driver-utils";
20
20
  import { IOdspSnapshot, ISnapshotCachedEntry, IVersionedValueWithEpoch, persistedCacheValueVersion } from "./contracts";
21
21
  import { getQueryString } from "./getQueryString";
22
22
  import { getUrlAndHeadersWithAuth } from "./getUrlAndHeadersWithAuth";
@@ -26,12 +26,13 @@ import {
26
26
  getWithRetryForTokenRefresh,
27
27
  getWithRetryForTokenRefreshRepeat,
28
28
  IOdspResponse,
29
- ISnapshotContents,
30
29
  } from "./odspUtils";
31
- import { convertOdspSnapshotToSnapsohtTreeAndBlobs } from "./odspSnapshotParser";
30
+ import { ISnapshotContents } from "./odspPublicUtils";
31
+ import { convertOdspSnapshotToSnapshotTreeAndBlobs } from "./odspSnapshotParser";
32
32
  import { currentReadVersion, parseCompactSnapshotResponse } from "./compactSnapshotParser";
33
33
  import { ReadBuffer } from "./ReadBufferUtils";
34
34
  import { EpochTracker } from "./epochTracker";
35
+ import { pkgVersion } from "./packageVersion";
35
36
 
36
37
  /**
37
38
  * Enum to support different types of snapshot formats.
@@ -83,7 +84,7 @@ export async function fetchSnapshot(
83
84
  },
84
85
  async () => snapshotDownloader(url, { headers }),
85
86
  ) as IOdspResponse<IOdspSnapshot>;
86
- return convertOdspSnapshotToSnapsohtTreeAndBlobs(response.content);
87
+ return convertOdspSnapshotToSnapshotTreeAndBlobs(response.content);
87
88
  }
88
89
 
89
90
  export async function fetchSnapshotWithRedeem(
@@ -93,11 +94,11 @@ export async function fetchSnapshotWithRedeem(
93
94
  forceAccessTokenViaAuthorizationHeader: boolean,
94
95
  logger: ITelemetryLogger,
95
96
  snapshotDownloader: (
96
- finalOdspResolvedUrl: IOdspResolvedUrl,
97
- storageToken: string,
98
- snapshotOptions: ISnapshotOptions | undefined,
99
- controller?: AbortController,
100
- ) => Promise<ISnapshotRequestAndResponseOptions>,
97
+ finalOdspResolvedUrl: IOdspResolvedUrl,
98
+ storageToken: string,
99
+ snapshotOptions: ISnapshotOptions | undefined,
100
+ controller?: AbortController,
101
+ ) => Promise<ISnapshotRequestAndResponseOptions>,
101
102
  putInCache: (valueWithEpoch: IVersionedValueWithEpoch) => Promise<void>,
102
103
  removeEntries: () => Promise<void>,
103
104
  enableRedeemFallback?: boolean,
@@ -126,12 +127,13 @@ export async function fetchSnapshotWithRedeem(
126
127
  await redeemSharingLink(
127
128
  odspResolvedUrl, storageTokenFetcher, logger, forceAccessTokenViaAuthorizationHeader);
128
129
  const odspResolvedUrlWithoutShareLink: IOdspResolvedUrl =
129
- { ...odspResolvedUrl,
130
- shareLinkInfo: {
131
- ...odspResolvedUrl.shareLinkInfo,
132
- sharingLinkToRedeem: undefined,
133
- },
134
- };
130
+ {
131
+ ...odspResolvedUrl,
132
+ shareLinkInfo: {
133
+ ...odspResolvedUrl.shareLinkInfo,
134
+ sharingLinkToRedeem: undefined,
135
+ },
136
+ };
135
137
 
136
138
  return fetchLatestSnapshotCore(
137
139
  odspResolvedUrlWithoutShareLink,
@@ -168,15 +170,15 @@ async function redeemSharingLink(
168
170
  eventName: "RedeemShareLink",
169
171
  },
170
172
  async () => getWithRetryForTokenRefresh(async (tokenFetchOptions) => {
171
- assert(!!odspResolvedUrl.shareLinkInfo?.sharingLinkToRedeem,
172
- 0x1ed /* "Share link should be present" */);
173
- const storageToken = await storageTokenFetcher(tokenFetchOptions, "RedeemShareLink");
174
- const encodedShareUrl = getEncodedShareUrl(odspResolvedUrl.shareLinkInfo?.sharingLinkToRedeem);
175
- const redeemUrl = `${odspResolvedUrl.siteUrl}/_api/v2.0/shares/${encodedShareUrl}`;
176
- const { url, headers } = getUrlAndHeadersWithAuth(
177
- redeemUrl, storageToken, forceAccessTokenViaAuthorizationHeader);
178
- headers.prefer = "redeemSharingLink";
179
- return fetchAndParseAsJSONHelper(url, { headers });
173
+ assert(!!odspResolvedUrl.shareLinkInfo?.sharingLinkToRedeem,
174
+ 0x1ed /* "Share link should be present" */);
175
+ const storageToken = await storageTokenFetcher(tokenFetchOptions, "RedeemShareLink");
176
+ const encodedShareUrl = getEncodedShareUrl(odspResolvedUrl.shareLinkInfo?.sharingLinkToRedeem);
177
+ const redeemUrl = `${odspResolvedUrl.siteUrl}/_api/v2.0/shares/${encodedShareUrl}`;
178
+ const { url, headers } = getUrlAndHeadersWithAuth(
179
+ redeemUrl, storageToken, forceAccessTokenViaAuthorizationHeader);
180
+ headers.prefer = "redeemSharingLink";
181
+ return fetchAndParseAsJSONHelper(url, { headers });
180
182
  }),
181
183
  );
182
184
  }
@@ -187,11 +189,11 @@ async function fetchLatestSnapshotCore(
187
189
  snapshotOptions: ISnapshotOptions | undefined,
188
190
  logger: ITelemetryLogger,
189
191
  snapshotDownloader: (
190
- finalOdspResolvedUrl: IOdspResolvedUrl,
191
- storageToken: string,
192
- snapshotOptions: ISnapshotOptions | undefined,
193
- controller?: AbortController,
194
- ) => Promise<ISnapshotRequestAndResponseOptions>,
192
+ finalOdspResolvedUrl: IOdspResolvedUrl,
193
+ storageToken: string,
194
+ snapshotOptions: ISnapshotOptions | undefined,
195
+ controller?: AbortController,
196
+ ) => Promise<ISnapshotRequestAndResponseOptions>,
195
197
  putInCache: (valueWithEpoch: IVersionedValueWithEpoch) => Promise<void>,
196
198
  enableRedeemFallback?: boolean,
197
199
  ): Promise<ISnapshotContents> {
@@ -232,7 +234,64 @@ async function fetchLatestSnapshotCore(
232
234
  snapshotOptions,
233
235
  controller,
234
236
  );
235
- const snapshot = response.odspSnapshotResponse.content;
237
+ const odspResponse = response.odspResponse;
238
+ const contentType = odspResponse.headers.get("content-type");
239
+ odspResponse.propsToLog = {
240
+ ...odspResponse.propsToLog,
241
+ contentType,
242
+ accept: response.requestHeaders.accept,
243
+ };
244
+ let parsedSnapshotContents: IOdspResponse<ISnapshotContents> | undefined;
245
+ try {
246
+ switch (contentType) {
247
+ case "application/json": {
248
+ const text = await odspResponse.content.text();
249
+ odspResponse.propsToLog.bodySize = text.length;
250
+ const content: IOdspSnapshot = JSON.parse(text);
251
+ validateBlobsAndTrees(content);
252
+ const snapshotContents: ISnapshotContents =
253
+ convertOdspSnapshotToSnapshotTreeAndBlobs(content);
254
+ parsedSnapshotContents = { ...odspResponse, content: snapshotContents };
255
+ break;
256
+ }
257
+ case "application/ms-fluid": {
258
+ const content = await odspResponse.content.arrayBuffer();
259
+ odspResponse.propsToLog.bodySize = content.byteLength;
260
+ const snapshotContents: ISnapshotContents = parseCompactSnapshotResponse(
261
+ new ReadBuffer(new Uint8Array(content)));
262
+ if (snapshotContents.snapshotTree.trees === undefined ||
263
+ snapshotContents.snapshotTree.blobs === undefined) {
264
+ throw new NonRetryableError(
265
+ "Returned odsp snapshot is malformed. No trees or blobs!",
266
+ DriverErrorType.incorrectServerResponse,
267
+ { driverVersion: pkgVersion, ...odspResponse.propsToLog },
268
+ );
269
+ }
270
+ parsedSnapshotContents = { ...odspResponse, content: snapshotContents };
271
+ break;
272
+ }
273
+ default:
274
+ throw new NonRetryableError(
275
+ "Unknown snapshot content type",
276
+ DriverErrorType.incorrectServerResponse,
277
+ { driverVersion: pkgVersion, ...odspResponse.propsToLog },
278
+ );
279
+ }
280
+ } catch (error) {
281
+ if (isFluidError(error)) {
282
+ error.addTelemetryProperties({ driverVersion: pkgVersion, ...odspResponse.propsToLog });
283
+ throw error;
284
+ }
285
+ const enhancedError = wrapError(
286
+ error,
287
+ (errorMessage) => new NonRetryableError(
288
+ `Error parsing snapshot response: ${errorMessage}`,
289
+ DriverErrorType.genericError,
290
+ { driverVersion: pkgVersion, ...odspResponse.propsToLog }));
291
+ throw enhancedError;
292
+ }
293
+ assert(parsedSnapshotContents !== undefined, 0x312 /* snapshot should be parsed */);
294
+ const snapshot = parsedSnapshotContents.content;
236
295
  // From: https://developer.mozilla.org/en-US/docs/Web/API/PerformanceResourceTiming
237
296
  // fetchStart: immediately before the browser starts to fetch the resource.
238
297
  // requestStart: immediately before the browser starts requesting the resource from the server
@@ -254,7 +313,7 @@ async function fetchLatestSnapshotCore(
254
313
  let fetchStartToResponseEndTime: number | undefined; // responseEnd - fetchStart
255
314
  let reqStartToResponseEndTime: number | undefined; // responseEnd - requestStart
256
315
  let networkTime: number | undefined; // responseEnd - startTime
257
- const spReqDuration = response.odspSnapshotResponse.headers.get("sprequestduration");
316
+ const spReqDuration = odspResponse.headers.get("sprequestduration");
258
317
 
259
318
  // getEntriesByType is only available in browser performance object
260
319
  const resources1 = performance.getEntriesByType?.("resource") ?? [];
@@ -286,12 +345,12 @@ async function fetchLatestSnapshotCore(
286
345
  }
287
346
 
288
347
  const { numTrees, numBlobs, encodedBlobsSize } =
289
- validateAndEvalBlobsAndTrees(response.odspSnapshotResponse.content);
348
+ evalBlobsAndTrees(parsedSnapshotContents.content);
290
349
 
291
350
  // There are some scenarios in ODSP where we cannot cache, trees/latest will explicitly tell us when we
292
351
  // cannot cache using an HTTP response header.
293
352
  const canCache =
294
- response.odspSnapshotResponse.headers.get("disablebrowsercachingofusercontent") !== "true";
353
+ odspResponse.headers.get("disablebrowsercachingofusercontent") !== "true";
295
354
  const sequenceNumber: number = snapshot.sequenceNumber ?? 0;
296
355
  const seqNumberFromOps = snapshot.ops && snapshot.ops.length > 0 ?
297
356
  snapshot.ops[0].sequenceNumber - 1 :
@@ -302,7 +361,7 @@ async function fetchLatestSnapshotCore(
302
361
  logger.sendErrorEvent({ eventName: "fetchSnapshotError", sequenceNumber, seqNumberFromOps });
303
362
  snapshot.sequenceNumber = undefined;
304
363
  } else if (canCache) {
305
- const fluidEpoch = response.odspSnapshotResponse.headers.get("x-fluid-epoch");
364
+ const fluidEpoch = odspResponse.headers.get("x-fluid-epoch");
306
365
  assert(fluidEpoch !== undefined, 0x1e6 /* "Epoch should be present in response" */);
307
366
  const value: ISnapshotCachedEntry = {
308
367
  ...snapshot,
@@ -346,8 +405,8 @@ async function fetchLatestSnapshotCore(
346
405
  // Azure Fluid Relay service; desc=S, FRP; desc=False. Here, FRL is the duration taken for redeem,
347
406
  // Azure Fluid Relay service is the redeem status (S means success), and FRP is a flag to indicate
348
407
  // if the permission has changed.
349
- sltelemetry: response.odspSnapshotResponse.headers.get("x-fluid-sltelemetry"),
350
- ...response.odspSnapshotResponse.propsToLog,
408
+ sltelemetry: odspResponse.headers.get("x-fluid-sltelemetry"),
409
+ ...odspResponse.propsToLog,
351
410
  });
352
411
  return snapshot;
353
412
  },
@@ -363,8 +422,8 @@ async function fetchLatestSnapshotCore(
363
422
  });
364
423
  }
365
424
 
366
- interface ISnapshotRequestAndResponseOptions {
367
- odspSnapshotResponse: IOdspResponse<ISnapshotContents>;
425
+ export interface ISnapshotRequestAndResponseOptions {
426
+ odspResponse: IOdspResponse<Response>;
368
427
  requestUrl: string;
369
428
  requestHeaders: { [index: string]: any; };
370
429
  }
@@ -406,11 +465,7 @@ function getFormBodyAndHeaders(
406
465
  return { body: postBody, headers: header };
407
466
  }
408
467
 
409
- function validateAndEvalBlobsAndTrees(snapshot: ISnapshotContents) {
410
- assert(snapshot.snapshotTree !== undefined,
411
- 0x200 /* "Returned odsp snapshot is malformed. No trees!" */);
412
- assert(snapshot.blobs !== undefined,
413
- 0x201 /* "Returned odsp snapshot is malformed. No blobs!" */);
468
+ function evalBlobsAndTrees(snapshot: ISnapshotContents) {
414
469
  const numTrees = countTreesInSnapshotTree(snapshot.snapshotTree);
415
470
  const numBlobs = snapshot.blobs.size;
416
471
  let encodedBlobsSize = 0;
@@ -420,6 +475,13 @@ function validateAndEvalBlobsAndTrees(snapshot: ISnapshotContents) {
420
475
  return { numTrees, numBlobs, encodedBlobsSize };
421
476
  }
422
477
 
478
+ export function validateBlobsAndTrees(snapshot: IOdspSnapshot) {
479
+ assert(snapshot.trees !== undefined,
480
+ 0x200 /* "Returned odsp snapshot is malformed. No trees!" */);
481
+ assert(snapshot.blobs !== undefined,
482
+ 0x201 /* "Returned odsp snapshot is malformed. No blobs!" */);
483
+ }
484
+
423
485
  function countTreesInSnapshotTree(snapshotTree: ISnapshotTree): number {
424
486
  let numTrees = 0;
425
487
  for (const [_, tree] of Object.entries(snapshotTree.trees)) {
@@ -448,6 +510,7 @@ export async function downloadSnapshot(
448
510
  snapshotFormatFetchType?: SnapshotFormatSupportType,
449
511
  controller?: AbortController,
450
512
  epochTracker?: EpochTracker,
513
+ scenarioName?: string,
451
514
  ): Promise<ISnapshotRequestAndResponseOptions> {
452
515
  // back-compat: This block to be removed with #8784 when we only consume/consider odsp resolvers that are >= 0.51
453
516
  const sharingLinkToRedeem = (odspResolvedUrl as any).sharingLinkToRedeem;
@@ -482,28 +545,11 @@ export async function downloadSnapshot(
482
545
  headers.accept = "application/json";
483
546
  }
484
547
 
485
- const response = await (epochTracker?.fetch(url, fetchOptions, "treesLatest", true) ??
548
+ const odspResponse = await (epochTracker?.fetch(url, fetchOptions, "treesLatest", true, scenarioName) ??
486
549
  fetchHelper(url, fetchOptions));
487
550
 
488
- let finalSnapshotContents: IOdspResponse<ISnapshotContents>;
489
- const contentType = response.headers.get("content-type");
490
- if (contentType === "application/json") {
491
- const text = await response.content.text();
492
- const content: IOdspSnapshot = JSON.parse(text);
493
- response.propsToLog.bodySize = text.length;
494
- const snapshotContents: ISnapshotContents = convertOdspSnapshotToSnapsohtTreeAndBlobs(content);
495
- finalSnapshotContents = { ...response, content: snapshotContents };
496
- } else {
497
- assert(contentType === "application/ms-fluid", 0x2c3 /* "Content type should be application/ms-fluid" */);
498
- const content = await response.content.arrayBuffer();
499
- response.propsToLog.bodySize = content.byteLength;
500
- const snapshotContents: ISnapshotContents = parseCompactSnapshotResponse(
501
- new ReadBuffer(new Uint8Array(content)));
502
- finalSnapshotContents = { ...response, content: snapshotContents };
503
- }
504
- response.propsToLog.contentType = contentType;
505
551
  return {
506
- odspSnapshotResponse: finalSnapshotContents,
552
+ odspResponse,
507
553
  requestHeaders: headers,
508
554
  requestUrl: url,
509
555
  };
@@ -513,7 +559,7 @@ function isRedeemSharingLinkError(odspResolvedUrl: IOdspResolvedUrl, error: any)
513
559
  if (odspResolvedUrl.shareLinkInfo?.sharingLinkToRedeem !== undefined
514
560
  && (typeof error === "object" && error !== null)
515
561
  && (error.errorType === DriverErrorType.authorizationError
516
- || error.errorType === DriverErrorType.fileNotFoundOrAccessDeniedError)) {
562
+ || error.errorType === DriverErrorType.fileNotFoundOrAccessDeniedError)) {
517
563
  return true;
518
564
  }
519
565
  return false;
@@ -526,9 +572,9 @@ function getEncodedShareUrl(url: string): string {
526
572
  */
527
573
  let encodedUrl = fromUtf8ToBase64(encodeURI(url));
528
574
  encodedUrl = encodedUrl
529
- .replace(/=+$/g, "")
530
- .replace(/\//g, "_")
531
- .replace(/\+/g, "-");
575
+ .replace(/=+$/g, "")
576
+ .replace(/\//g, "_")
577
+ .replace(/\+/g, "-");
532
578
  encodedUrl = "u!".concat(encodedUrl);
533
579
  return encodedUrl;
534
580
  }
package/src/index.ts CHANGED
@@ -31,3 +31,7 @@ export * from "./odspDriverUrlResolver";
31
31
 
32
32
  // It's used by URL resolve code, but also has some public functions
33
33
  export * from "./odspFluidFileLink";
34
+
35
+ // Wire format related
36
+ export * from "./compactSnapshotParser";
37
+ export * from "./ReadBufferUtils";
@@ -128,7 +128,7 @@ class SocketReference extends TypedEventEmitter<ISocketEvents> {
128
128
  this.isPendingInitialConnection = false;
129
129
 
130
130
  // Explicitly cast error to the specified event args type to ensure type compatibility
131
- this.emit("server_disconnect", error as IFluidErrorBase & OdspError);
131
+ this.emit("server_disconnect", error);
132
132
  this.closeSocket();
133
133
  });
134
134
  }
@@ -440,7 +440,7 @@ export class OdspDocumentDeltaConnection extends DocumentDeltaConnection {
440
440
  }
441
441
 
442
442
  protected serverDisconnectHandler = (error: IFluidErrorBase & OdspError) => {
443
- this.logger.sendTelemetryEvent({ eventName: "ServerDisconnect" }, error);
443
+ this.logger.sendTelemetryEvent({ eventName: "ServerDisconnect", clientId: this.clientId }, error);
444
444
  this.disposeCore(true, error);
445
445
  };
446
446
 
@@ -460,7 +460,7 @@ export class OdspDocumentDeltaConnection extends DocumentDeltaConnection {
460
460
  };
461
461
  }
462
462
 
463
- this.socketReference!.once("server_disconnect", this.serverDisconnectHandler);
463
+ this.socketReference!.on("server_disconnect", this.serverDisconnectHandler);
464
464
 
465
465
  this.socket.on("get_ops_response", (result: IGetOpsResponse) => {
466
466
  const messages = result.messages;
@@ -553,20 +553,20 @@ export class OdspDocumentDeltaConnection extends DocumentDeltaConnection {
553
553
  case "nack":
554
554
  // per client / document nack handling
555
555
  super.addTrackedListener(event, (clientIdOrDocumentId: string, nacks: INack[]) => {
556
- if (clientIdOrDocumentId.length === 0 ||
556
+ const handle = clientIdOrDocumentId.length === 0 ||
557
557
  clientIdOrDocumentId === this.documentId ||
558
- (this.hasDetails && clientIdOrDocumentId === this.clientId)) {
559
- const nackContent = nacks[0]?.content;
560
- if (nackContent !== undefined) {
561
- const { code, type, message, retryAfter } = nackContent;
562
- this.logger.sendTelemetryEvent({
563
- eventName: "ServerNack",
564
- code,
565
- type,
566
- message,
567
- retryAfterSeconds: retryAfter,
568
- });
569
- }
558
+ (clientIdOrDocumentId === this.clientId);
559
+ const { code, type, message, retryAfter } = nacks[0]?.content ?? {};
560
+ this.logger.sendTelemetryEvent({
561
+ eventName: "ServerNack",
562
+ code,
563
+ type,
564
+ message,
565
+ retryAfterSeconds: retryAfter,
566
+ clientId: this.clientId,
567
+ handle,
568
+ });
569
+ if (handle) {
570
570
  this.emit("nack", clientIdOrDocumentId, nacks);
571
571
  }
572
572
  });
@@ -584,9 +584,9 @@ export class OdspDocumentDeltaConnection extends DocumentDeltaConnection {
584
584
  protected disconnect(socketProtocolError: boolean, reason: IAnyDriverError) {
585
585
  const socket = this.socketReference;
586
586
  assert(socket !== undefined, 0x0a2 /* "reentrancy not supported!" */);
587
- this.socketReference = undefined;
588
587
 
589
- this.socket.off("server_disconnect", this.serverDisconnectHandler);
588
+ this.socketReference?.off("server_disconnect", this.serverDisconnectHandler);
589
+ this.socketReference = undefined;
590
590
 
591
591
  if (!socketProtocolError && this.hasDetails) {
592
592
  // tell the server we are disconnecting this client from the document
@@ -21,7 +21,7 @@ import {
21
21
  IDocumentServicePolicies,
22
22
  DriverErrorType,
23
23
  } from "@fluidframework/driver-definitions";
24
- import { DeltaStreamConnectionForbiddenError, NonRetryableError } from "@fluidframework/driver-utils";
24
+ import { canRetryOnError, DeltaStreamConnectionForbiddenError, NonRetryableError } from "@fluidframework/driver-utils";
25
25
  import { IFacetCodes } from "@fluidframework/odsp-doclib-utils";
26
26
  import {
27
27
  IClient,
@@ -415,12 +415,16 @@ export class OdspDocumentService implements IDocumentService {
415
415
  if (response.refreshAfterDeltaMs > 0) {
416
416
  this.scheduleJoinSessionRefresh(response.refreshAfterDeltaMs)
417
417
  .catch((error) => {
418
- this.mc.logger.sendErrorEvent({
418
+ const canRetry = canRetryOnError(error);
419
+ // Only record error event in case it is non retriable.
420
+ if (!canRetry) {
421
+ this.mc.logger.sendErrorEvent({
419
422
  eventName: "JoinSessionRefreshError",
420
423
  details: JSON.stringify(props),
421
424
  },
422
425
  error,
423
- );
426
+ );
427
+ }
424
428
  });
425
429
  } else {
426
430
  // Logging just for informational purposes to help with debugging as this is a new feature.
@@ -102,7 +102,8 @@ export class OdspDocumentServiceFactoryCore implements IDocumentServiceFactory {
102
102
  this.persistedCache,
103
103
  this.nonPersistentCache,
104
104
  fileEntry,
105
- odspLogger);
105
+ odspLogger,
106
+ clientIsSummarizer);
106
107
 
107
108
  return PerformanceEvent.timedExecAsync(
108
109
  odspLogger,
@@ -189,7 +190,8 @@ export class OdspDocumentServiceFactoryCore implements IDocumentServiceFactory {
189
190
  this.persistedCache,
190
191
  this.nonPersistentCache,
191
192
  { resolvedUrl: odspResolvedUrl, docId: odspResolvedUrl.hashedDocumentId },
192
- odspLogger);
193
+ odspLogger,
194
+ clientIsSummarizer);
193
195
 
194
196
  const storageTokenFetcher = toInstrumentedOdspTokenFetcher(
195
197
  odspLogger,
@@ -38,8 +38,8 @@ import { IOdspCache } from "./odspCache";
38
38
  import {
39
39
  createCacheSnapshotKey,
40
40
  getWithRetryForTokenRefresh,
41
- ISnapshotContents,
42
41
  } from "./odspUtils";
42
+ import { ISnapshotContents } from "./odspPublicUtils";
43
43
  import { defaultCacheExpiryTimeoutMs, EpochTracker } from "./epochTracker";
44
44
  import { OdspSummaryUploadManager } from "./odspSummaryUploadManager";
45
45
  import { FlushResult } from "./odspDocumentDeltaConnection";
@@ -275,7 +275,7 @@ export class OdspDocumentStorageService implements IDocumentStorageService {
275
275
  method: "POST",
276
276
  },
277
277
  "createBlob",
278
- ));
278
+ ));
279
279
  event.end({
280
280
  blobId: res.content.id,
281
281
  ...res.propsToLog,
@@ -343,14 +343,14 @@ export class OdspDocumentStorageService implements IDocumentStorageService {
343
343
  return this.readBlobCore(blobId);
344
344
  }
345
345
 
346
- public async getSnapshotTree(version?: api.IVersion): Promise<api.ISnapshotTree | null> {
346
+ public async getSnapshotTree(version?: api.IVersion, scenarioName?: string): Promise<api.ISnapshotTree | null> {
347
347
  if (!this.snapshotUrl) {
348
348
  return null;
349
349
  }
350
350
 
351
351
  let id: string;
352
352
  if (!version || !version.id) {
353
- const versions = await this.getVersions(null, 1);
353
+ const versions = await this.getVersions(null, 1, scenarioName);
354
354
  if (!versions || versions.length === 0) {
355
355
  return null;
356
356
  }
@@ -359,7 +359,7 @@ export class OdspDocumentStorageService implements IDocumentStorageService {
359
359
  id = version.id;
360
360
  }
361
361
 
362
- const snapshotTree = await this.readTree(id);
362
+ const snapshotTree = await this.readTree(id, scenarioName);
363
363
  if (!snapshotTree) {
364
364
  return null;
365
365
  }
@@ -379,7 +379,7 @@ export class OdspDocumentStorageService implements IDocumentStorageService {
379
379
  return this.combineProtocolAndAppSnapshotTree(appTree, protocolTree);
380
380
  }
381
381
 
382
- public async getVersions(blobid: string | null, count: number): Promise<api.IVersion[]> {
382
+ public async getVersions(blobid: string | null, count: number, scenarioName?: string): Promise<api.IVersion[]> {
383
383
  // Regular load workflow uses blobId === documentID to indicate "latest".
384
384
  if (blobid !== this.documentId && blobid) {
385
385
  // FluidFetch & FluidDebugger tools use empty sting to query for versions
@@ -437,14 +437,14 @@ export class OdspDocumentStorageService implements IDocumentStorageService {
437
437
  }
438
438
 
439
439
  return snapshotCachedEntry;
440
- });
440
+ });
441
441
 
442
442
  // Based on the concurrentSnapshotFetch policy:
443
443
  // Either retrieve both the network and cache snapshots concurrently and pick the first to return,
444
444
  // or grab the cache value and then the network value if the cache value returns undefined.
445
445
  let method: string;
446
446
  if (this.hostPolicy.concurrentSnapshotFetch && !this.hostPolicy.summarizerClient) {
447
- const networkSnapshotP = this.fetchSnapshot(hostSnapshotOptions);
447
+ const networkSnapshotP = this.fetchSnapshot(hostSnapshotOptions, scenarioName);
448
448
 
449
449
  // Ensure that failures on both paths are ignored initially.
450
450
  // I.e. if cache fails for some reason, we will proceed with network result.
@@ -478,7 +478,7 @@ export class OdspDocumentStorageService implements IDocumentStorageService {
478
478
  method = retrievedSnapshot !== undefined ? "cache" : "network";
479
479
 
480
480
  if (retrievedSnapshot === undefined) {
481
- retrievedSnapshot = await this.fetchSnapshot(hostSnapshotOptions);
481
+ retrievedSnapshot = await this.fetchSnapshot(hostSnapshotOptions, scenarioName);
482
482
  }
483
483
  }
484
484
  if (method === "network") {
@@ -524,7 +524,7 @@ export class OdspDocumentStorageService implements IDocumentStorageService {
524
524
  eventName: "getVersions",
525
525
  headers: Object.keys(headers).length !== 0 ? true : undefined,
526
526
  },
527
- async () => this.epochTracker.fetchAndParseAsJSON<IDocumentStorageGetVersionsResponse>(url, { headers }, "versions"),
527
+ async () => this.epochTracker.fetchAndParseAsJSON<IDocumentStorageGetVersionsResponse>(url, { headers }, "versions", undefined, scenarioName),
528
528
  );
529
529
  const versionsResponse = response.content;
530
530
  if (!versionsResponse) {
@@ -548,8 +548,8 @@ export class OdspDocumentStorageService implements IDocumentStorageService {
548
548
  });
549
549
  }
550
550
 
551
- private async fetchSnapshot(hostSnapshotOptions: ISnapshotOptions | undefined) {
552
- return this.fetchSnapshotCore(hostSnapshotOptions).catch((error) => {
551
+ private async fetchSnapshot(hostSnapshotOptions: ISnapshotOptions | undefined, scenarioName?: string) {
552
+ return this.fetchSnapshotCore(hostSnapshotOptions, scenarioName).catch((error) => {
553
553
  // Issue #5895:
554
554
  // If we are offline, this error is retryable. But that means that RetriableDocumentStorageService
555
555
  // will run in circles calling getSnapshotTree, which would result in OdspDocumentStorageService class
@@ -562,7 +562,7 @@ export class OdspDocumentStorageService implements IDocumentStorageService {
562
562
  });
563
563
  }
564
564
 
565
- private async fetchSnapshotCore(hostSnapshotOptions: ISnapshotOptions | undefined) {
565
+ private async fetchSnapshotCore(hostSnapshotOptions: ISnapshotOptions | undefined, scenarioName?: string) {
566
566
  const snapshotOptions: ISnapshotOptions = {
567
567
  mds: this.maxSnapshotSizeLimit,
568
568
  ...hostSnapshotOptions,
@@ -589,6 +589,7 @@ export class OdspDocumentStorageService implements IDocumentStorageService {
589
589
  this.snapshotFormatFetchType,
590
590
  controller,
591
591
  this.epochTracker,
592
+ scenarioName,
592
593
  );
593
594
  };
594
595
  const putInCache = async (valueWithEpoch: IVersionedValueWithEpoch) => {
@@ -642,19 +643,13 @@ export class OdspDocumentStorageService implements IDocumentStorageService {
642
643
  }
643
644
  }
644
645
 
645
- public async write(tree: api.ITree, parents: string[], message: string): Promise<api.IVersion> {
646
- this.checkSnapshotUrl();
647
-
648
- throw new Error("Not supported");
649
- }
650
-
651
646
  public async uploadSummaryWithContext(summary: api.ISummaryTree, context: ISummaryContext): Promise<string> {
652
647
  this.checkSnapshotUrl();
653
648
 
654
649
  // Enable flushing only if we have single commit summary and this is not the initial summary for an empty file
655
650
  if (".protocol" in summary.tree && context.ackHandle !== undefined) {
656
651
  let retry = 0;
657
- for (;;) {
652
+ for (; ;) {
658
653
  const result = await this.flushCallback();
659
654
  const seq = result.lastPersistedSequenceNumber;
660
655
  if (seq !== undefined && seq >= context.referenceSequenceNumber) {
@@ -726,7 +721,7 @@ export class OdspDocumentStorageService implements IDocumentStorageService {
726
721
  }
727
722
  }
728
723
 
729
- private async readTree(id: string): Promise<api.ISnapshotTree | null> {
724
+ private async readTree(id: string, scenarioName?: string): Promise<api.ISnapshotTree | null> {
730
725
  if (!this.snapshotUrl) {
731
726
  return null;
732
727
  }
@@ -739,6 +734,8 @@ export class OdspDocumentStorageService implements IDocumentStorageService {
739
734
  url,
740
735
  fetchOptions,
741
736
  "snapshotTree",
737
+ undefined,
738
+ scenarioName,
742
739
  );
743
740
  };
744
741
  const snapshot = await fetchSnapshot(