@fluidframework/odsp-driver 0.52.1 → 0.54.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 (112) hide show
  1. package/dist/createFile.d.ts.map +1 -1
  2. package/dist/createFile.js +5 -4
  3. package/dist/createFile.js.map +1 -1
  4. package/dist/epochTracker.d.ts +2 -1
  5. package/dist/epochTracker.d.ts.map +1 -1
  6. package/dist/epochTracker.js +50 -21
  7. package/dist/epochTracker.js.map +1 -1
  8. package/dist/fetchSnapshot.d.ts.map +1 -1
  9. package/dist/fetchSnapshot.js +15 -14
  10. package/dist/fetchSnapshot.js.map +1 -1
  11. package/dist/getFileLink.js +3 -3
  12. package/dist/getFileLink.js.map +1 -1
  13. package/dist/odspDeltaStorageService.d.ts +3 -3
  14. package/dist/odspDeltaStorageService.d.ts.map +1 -1
  15. package/dist/odspDeltaStorageService.js +7 -4
  16. package/dist/odspDeltaStorageService.js.map +1 -1
  17. package/dist/odspDocumentDeltaConnection.d.ts.map +1 -1
  18. package/dist/odspDocumentDeltaConnection.js +2 -0
  19. package/dist/odspDocumentDeltaConnection.js.map +1 -1
  20. package/dist/odspDocumentService.d.ts +1 -1
  21. package/dist/odspDocumentService.d.ts.map +1 -1
  22. package/dist/odspDocumentService.js +13 -25
  23. package/dist/odspDocumentService.js.map +1 -1
  24. package/dist/odspDocumentStorageManager.d.ts.map +1 -1
  25. package/dist/odspDocumentStorageManager.js +29 -26
  26. package/dist/odspDocumentStorageManager.js.map +1 -1
  27. package/dist/odspDriverUrlResolverForShareLink.d.ts.map +1 -1
  28. package/dist/odspDriverUrlResolverForShareLink.js +4 -3
  29. package/dist/odspDriverUrlResolverForShareLink.js.map +1 -1
  30. package/dist/odspError.d.ts.map +1 -1
  31. package/dist/odspError.js +3 -1
  32. package/dist/odspError.js.map +1 -1
  33. package/dist/odspSummaryUploadManager.d.ts +1 -1
  34. package/dist/odspSummaryUploadManager.d.ts.map +1 -1
  35. package/dist/odspSummaryUploadManager.js +7 -20
  36. package/dist/odspSummaryUploadManager.js.map +1 -1
  37. package/dist/odspUtils.d.ts.map +1 -1
  38. package/dist/odspUtils.js +31 -22
  39. package/dist/odspUtils.js.map +1 -1
  40. package/dist/packageVersion.d.ts +1 -1
  41. package/dist/packageVersion.js +1 -1
  42. package/dist/packageVersion.js.map +1 -1
  43. package/dist/vroom.d.ts.map +1 -1
  44. package/dist/vroom.js +4 -1
  45. package/dist/vroom.js.map +1 -1
  46. package/dist/zipItDataRepresentationUtils.d.ts.map +1 -1
  47. package/dist/zipItDataRepresentationUtils.js +3 -3
  48. package/dist/zipItDataRepresentationUtils.js.map +1 -1
  49. package/lib/createFile.d.ts.map +1 -1
  50. package/lib/createFile.js +6 -5
  51. package/lib/createFile.js.map +1 -1
  52. package/lib/epochTracker.d.ts +2 -1
  53. package/lib/epochTracker.d.ts.map +1 -1
  54. package/lib/epochTracker.js +50 -21
  55. package/lib/epochTracker.js.map +1 -1
  56. package/lib/fetchSnapshot.d.ts.map +1 -1
  57. package/lib/fetchSnapshot.js +15 -14
  58. package/lib/fetchSnapshot.js.map +1 -1
  59. package/lib/getFileLink.js +4 -4
  60. package/lib/getFileLink.js.map +1 -1
  61. package/lib/odspDeltaStorageService.d.ts +3 -3
  62. package/lib/odspDeltaStorageService.d.ts.map +1 -1
  63. package/lib/odspDeltaStorageService.js +7 -4
  64. package/lib/odspDeltaStorageService.js.map +1 -1
  65. package/lib/odspDocumentDeltaConnection.d.ts.map +1 -1
  66. package/lib/odspDocumentDeltaConnection.js +2 -0
  67. package/lib/odspDocumentDeltaConnection.js.map +1 -1
  68. package/lib/odspDocumentService.d.ts +1 -1
  69. package/lib/odspDocumentService.d.ts.map +1 -1
  70. package/lib/odspDocumentService.js +15 -27
  71. package/lib/odspDocumentService.js.map +1 -1
  72. package/lib/odspDocumentStorageManager.d.ts.map +1 -1
  73. package/lib/odspDocumentStorageManager.js +31 -28
  74. package/lib/odspDocumentStorageManager.js.map +1 -1
  75. package/lib/odspDriverUrlResolverForShareLink.d.ts.map +1 -1
  76. package/lib/odspDriverUrlResolverForShareLink.js +5 -4
  77. package/lib/odspDriverUrlResolverForShareLink.js.map +1 -1
  78. package/lib/odspError.d.ts.map +1 -1
  79. package/lib/odspError.js +3 -1
  80. package/lib/odspError.js.map +1 -1
  81. package/lib/odspSummaryUploadManager.d.ts +1 -1
  82. package/lib/odspSummaryUploadManager.d.ts.map +1 -1
  83. package/lib/odspSummaryUploadManager.js +8 -21
  84. package/lib/odspSummaryUploadManager.js.map +1 -1
  85. package/lib/odspUtils.d.ts.map +1 -1
  86. package/lib/odspUtils.js +33 -24
  87. package/lib/odspUtils.js.map +1 -1
  88. package/lib/packageVersion.d.ts +1 -1
  89. package/lib/packageVersion.js +1 -1
  90. package/lib/packageVersion.js.map +1 -1
  91. package/lib/vroom.d.ts.map +1 -1
  92. package/lib/vroom.js +4 -1
  93. package/lib/vroom.js.map +1 -1
  94. package/lib/zipItDataRepresentationUtils.d.ts.map +1 -1
  95. package/lib/zipItDataRepresentationUtils.js +3 -3
  96. package/lib/zipItDataRepresentationUtils.js.map +1 -1
  97. package/package.json +8 -8
  98. package/src/createFile.ts +13 -9
  99. package/src/epochTracker.ts +50 -20
  100. package/src/fetchSnapshot.ts +15 -8
  101. package/src/getFileLink.ts +10 -4
  102. package/src/odspDeltaStorageService.ts +10 -3
  103. package/src/odspDocumentDeltaConnection.ts +2 -0
  104. package/src/odspDocumentService.ts +27 -29
  105. package/src/odspDocumentStorageManager.ts +48 -28
  106. package/src/odspDriverUrlResolverForShareLink.ts +8 -3
  107. package/src/odspError.ts +5 -1
  108. package/src/odspSummaryUploadManager.ts +7 -22
  109. package/src/odspUtils.ts +43 -34
  110. package/src/packageVersion.ts +1 -1
  111. package/src/vroom.ts +6 -1
  112. package/src/zipItDataRepresentationUtils.ts +4 -7
@@ -5,7 +5,11 @@
5
5
 
6
6
  import { ITelemetryLogger } from "@fluidframework/common-definitions";
7
7
  import { performance } from "@fluidframework/common-utils";
8
- import { ChildLogger, TelemetryLogger } from "@fluidframework/telemetry-utils";
8
+ import {
9
+ ChildLogger,
10
+ loggerToMonitoringContext,
11
+ MonitoringContext,
12
+ } from "@fluidframework/telemetry-utils";
9
13
  import {
10
14
  IDocumentDeltaConnection,
11
15
  IDocumentDeltaStorageService,
@@ -14,8 +18,8 @@ import {
14
18
  IDocumentStorageService,
15
19
  IDocumentServicePolicies,
16
20
  } from "@fluidframework/driver-definitions";
17
- import { DeltaStreamConnectionForbiddenError } from "@fluidframework/driver-utils";
18
- import { fetchTokenErrorCode, IFacetCodes, throwOdspNetworkError } from "@fluidframework/odsp-doclib-utils";
21
+ import { DeltaStreamConnectionForbiddenError, NonRetryableError } from "@fluidframework/driver-utils";
22
+ import { IFacetCodes } from "@fluidframework/odsp-doclib-utils";
19
23
  import {
20
24
  IClient,
21
25
  ISequencedDocumentMessage,
@@ -26,6 +30,7 @@ import {
26
30
  IEntry,
27
31
  HostStoragePolicy,
28
32
  InstrumentedStorageTokenFetcher,
33
+ OdspErrorType,
29
34
  } from "@fluidframework/odsp-driver-definitions";
30
35
  import { HostStoragePolicyInternal, ISocketStorageDiscovery } from "./contracts";
31
36
  import { IOdspCache } from "./odspCache";
@@ -39,18 +44,6 @@ import { EpochTracker } from "./epochTracker";
39
44
  import { OpsCache } from "./opsCaching";
40
45
  import { RetryErrorsStorageAdapter } from "./retryErrorsStorageAdapter";
41
46
 
42
- // Gate that when set to "1", instructs to fetch the binary format snapshot from the spo.
43
- function gatesBinaryFormatSnapshot() {
44
- try {
45
- if (typeof localStorage === "object" && localStorage !== null) {
46
- if (localStorage.binaryFormatSnapshot === "1") {
47
- return true;
48
- }
49
- }
50
- } catch (e) {}
51
- return false;
52
- }
53
-
54
47
  /**
55
48
  * The DocumentService manages the Socket.IO connection and manages routing requests to connected
56
49
  * clients
@@ -98,7 +91,7 @@ export class OdspDocumentService implements IDocumentService {
98
91
 
99
92
  private storageManager?: OdspDocumentStorageService;
100
93
 
101
- private readonly logger: TelemetryLogger;
94
+ private readonly mc: MonitoringContext;
102
95
 
103
96
  private readonly joinSessionKey: string;
104
97
 
@@ -139,16 +132,18 @@ export class OdspDocumentService implements IDocumentService {
139
132
  };
140
133
 
141
134
  this.joinSessionKey = `${this.odspResolvedUrl.hashedDocumentId}/joinsession`;
142
- this.logger = ChildLogger.create(logger,
135
+ this.mc = loggerToMonitoringContext(
136
+ ChildLogger.create(logger,
143
137
  undefined,
144
138
  {
145
139
  all: {
146
140
  odc: isOdcOrigin(new URL(this.odspResolvedUrl.endpoints.snapshotStorageUrl).origin),
147
141
  },
148
- });
142
+ }));
149
143
 
150
144
  this.hostPolicy = hostPolicy;
151
- this.hostPolicy.fetchBinarySnapshotFormat ??= gatesBinaryFormatSnapshot();
145
+ this.hostPolicy.fetchBinarySnapshotFormat ??=
146
+ this.mc.config.getBoolean("Fluid.Driver.Odsp.binaryFormatSnapshot");
152
147
  if (this.odspResolvedUrl.summarizer) {
153
148
  this.hostPolicy = { ...this.hostPolicy, summarizerClient: true };
154
149
  }
@@ -171,7 +166,7 @@ export class OdspDocumentService implements IDocumentService {
171
166
  this.storageManager = new OdspDocumentStorageService(
172
167
  this.odspResolvedUrl,
173
168
  this.getStorageToken,
174
- this.logger,
169
+ this.mc.logger,
175
170
  true,
176
171
  this.cache,
177
172
  this.hostPolicy,
@@ -186,7 +181,7 @@ export class OdspDocumentService implements IDocumentService {
186
181
  );
187
182
  }
188
183
 
189
- return new RetryErrorsStorageAdapter(this.storageManager, this.logger);
184
+ return new RetryErrorsStorageAdapter(this.storageManager, this.mc.logger);
190
185
  }
191
186
 
192
187
  /**
@@ -200,7 +195,7 @@ export class OdspDocumentService implements IDocumentService {
200
195
  this.odspResolvedUrl.endpoints.deltaStorageUrl,
201
196
  this.getStorageToken,
202
197
  this.epochTracker,
203
- this.logger,
198
+ this.mc.logger,
204
199
  );
205
200
 
206
201
  // batch size, please see issue #5211 for data around batch sizing
@@ -208,10 +203,10 @@ export class OdspDocumentService implements IDocumentService {
208
203
  const concurrency = this.hostPolicy.concurrentOpsBatches ?? 1;
209
204
  return new OdspDeltaStorageWithCache(
210
205
  snapshotOps,
211
- this.logger,
206
+ this.mc.logger,
212
207
  batchSize,
213
208
  concurrency,
214
- async (from, to, telemetryProps) => service.get(from, to, telemetryProps),
209
+ async (from, to, telemetryProps, fetchReason) => service.get(from, to, telemetryProps, fetchReason),
215
210
  async (from, to) => {
216
211
  const res = await this.opsCache?.get(from, to);
217
212
  return res as ISequencedDocumentMessage[] ?? [];
@@ -270,7 +265,10 @@ export class OdspDocumentService implements IDocumentService {
270
265
 
271
266
  const finalWebsocketToken = websocketToken ?? (websocketEndpoint.socketToken || null);
272
267
  if (finalWebsocketToken === null) {
273
- throwOdspNetworkError("pushTokenIsNull", fetchTokenErrorCode);
268
+ throw new NonRetryableError(
269
+ "pushTokenIsNull",
270
+ "Websocket token is null",
271
+ OdspErrorType.fetchTokenError);
274
272
  }
275
273
  try {
276
274
  const connection = await this.connectToDeltaStreamWithRetry(
@@ -304,7 +302,7 @@ export class OdspDocumentService implements IDocumentService {
304
302
  this.odspResolvedUrl,
305
303
  "opStream/joinSession",
306
304
  "POST",
307
- this.logger,
305
+ this.mc.logger,
308
306
  this.getStorageToken,
309
307
  this.epochTracker,
310
308
  requestSocketToken,
@@ -344,7 +342,7 @@ export class OdspDocumentService implements IDocumentService {
344
342
  io,
345
343
  client,
346
344
  webSocketUrl,
347
- this.logger,
345
+ this.mc.logger,
348
346
  60000,
349
347
  this.epochTracker,
350
348
  this.socketReferenceKeyPrefix,
@@ -354,7 +352,7 @@ export class OdspDocumentService implements IDocumentService {
354
352
  // Given that most reconnects result in reusing socket and happen very quickly,
355
353
  // report event only if it took longer than threshold.
356
354
  if (duration >= 2000) {
357
- this.logger.sendPerformanceEvent({
355
+ this.mc.logger.sendPerformanceEvent({
358
356
  eventName: "ConnectionSuccess",
359
357
  duration,
360
358
  });
@@ -391,7 +389,7 @@ export class OdspDocumentService implements IDocumentService {
391
389
  };
392
390
  this._opsCache = new OpsCache(
393
391
  seqNumber,
394
- this.logger,
392
+ this.mc.logger,
395
393
  // ICache
396
394
  {
397
395
  write: async (key: string, opsData: string) => {
@@ -17,9 +17,9 @@ import {
17
17
  ISummaryContext,
18
18
  IDocumentStorageService,
19
19
  LoaderCachingPolicy,
20
+ DriverErrorType,
20
21
  } from "@fluidframework/driver-definitions";
21
- import { RateLimiter } from "@fluidframework/driver-utils";
22
- import { throwOdspNetworkError } from "@fluidframework/odsp-doclib-utils";
22
+ import { RateLimiter, NonRetryableError } from "@fluidframework/driver-utils";
23
23
  import {
24
24
  IOdspResolvedUrl,
25
25
  ISnapshotOptions,
@@ -313,9 +313,6 @@ export class OdspDocumentStorageService implements IDocumentStorageService {
313
313
  this.blobCache.setBlob(blobId, blob);
314
314
  }
315
315
 
316
- if (!this.attributesBlobHandles.has(blobId)) {
317
- return blob;
318
- }
319
316
  return blob;
320
317
  }
321
318
 
@@ -406,22 +403,31 @@ export class OdspDocumentStorageService implements IDocumentStorageService {
406
403
  { eventName: "ObtainSnapshot" },
407
404
  async (event: PerformanceEvent) => {
408
405
  const props = {};
409
- let cachedSnapshot: ISnapshotContents | undefined;
406
+ let retrievedSnapshot: ISnapshotContents | undefined;
407
+ // Here's the logic to grab the persistent cache snapshot implemented by the host
408
+ // Epoch tracker is responsible for communicating with the persistent cache, handling epochs and cache versions
410
409
  const cachedSnapshotP: Promise<ISnapshotContents | undefined> =
411
410
  this.epochTracker.get(createCacheSnapshotKey(this.odspResolvedUrl))
412
- .then((snapshotCachedEntry: ISnapshotCachedEntry) => {
411
+ .then(async (snapshotCachedEntry: ISnapshotCachedEntry) => {
413
412
  if (snapshotCachedEntry !== undefined) {
414
413
  // If the cached entry does not contain the entry time, then assign it a default of 30 days old.
415
- // eslint-disable-next-line @typescript-eslint/dot-notation
416
- props["cacheEntryAge"] = Date.now() - (snapshotCachedEntry.cacheEntryTime ??
414
+ const age = Date.now() - (snapshotCachedEntry.cacheEntryTime ??
417
415
  (Date.now() - 30 * 24 * 60 * 60 * 1000));
416
+
417
+ // Record the cache age
418
+ // eslint-disable-next-line @typescript-eslint/dot-notation
419
+ props["cacheEntryAge"] = age;
418
420
  }
421
+
419
422
  return snapshotCachedEntry;
420
423
  });
421
424
 
425
+ // Based on the concurrentSnapshotFetch policy:
426
+ // Either retrieve both the network and cache snapshots concurrently and pick the first to return,
427
+ // or grab the cache value and then the network value if the cache value returns undefined.
422
428
  let method: string;
423
429
  if (this.hostPolicy.concurrentSnapshotFetch && !this.hostPolicy.summarizerClient) {
424
- const snapshotP = this.fetchSnapshot(hostSnapshotOptions);
430
+ const networkSnapshotP = this.fetchSnapshot(hostSnapshotOptions);
425
431
 
426
432
  // Ensure that failures on both paths are ignored initially.
427
433
  // I.e. if cache fails for some reason, we will proceed with network result.
@@ -429,20 +435,20 @@ export class OdspDocumentStorageService implements IDocumentStorageService {
429
435
  // do want to attempt to succeed with cached data!
430
436
  const promiseRaceWinner = await promiseRaceWithWinner([
431
437
  cachedSnapshotP.catch(() => undefined),
432
- snapshotP.catch(() => undefined),
438
+ networkSnapshotP.catch(() => undefined),
433
439
  ]);
434
- cachedSnapshot = promiseRaceWinner.value;
440
+ retrievedSnapshot = promiseRaceWinner.value;
435
441
  method = promiseRaceWinner.index === 0 ? "cache" : "network";
436
442
 
437
- if (cachedSnapshot === undefined) {
443
+ if (retrievedSnapshot === undefined) {
438
444
  // if network failed -> wait for cache ( then return network failure)
439
445
  // If cache returned empty or failed -> wait for network (success of failure)
440
446
  if (promiseRaceWinner.index === 1) {
441
- cachedSnapshot = await cachedSnapshotP;
447
+ retrievedSnapshot = await cachedSnapshotP;
442
448
  method = "cache";
443
449
  }
444
- if (cachedSnapshot === undefined) {
445
- cachedSnapshot = await snapshotP;
450
+ if (retrievedSnapshot === undefined) {
451
+ retrievedSnapshot = await networkSnapshotP;
446
452
  method = "network";
447
453
  }
448
454
  }
@@ -450,12 +456,12 @@ export class OdspDocumentStorageService implements IDocumentStorageService {
450
456
  // Note: There's a race condition here - another caller may come past the undefined check
451
457
  // while the first caller is awaiting later async code in this block.
452
458
 
453
- cachedSnapshot = await cachedSnapshotP;
459
+ retrievedSnapshot = await cachedSnapshotP;
454
460
 
455
- method = cachedSnapshot !== undefined ? "cache" : "network";
461
+ method = retrievedSnapshot !== undefined ? "cache" : "network";
456
462
 
457
- if (cachedSnapshot === undefined) {
458
- cachedSnapshot = await this.fetchSnapshot(hostSnapshotOptions);
463
+ if (retrievedSnapshot === undefined) {
464
+ retrievedSnapshot = await this.fetchSnapshot(hostSnapshotOptions);
459
465
  }
460
466
  }
461
467
  if (method === "network") {
@@ -463,12 +469,11 @@ export class OdspDocumentStorageService implements IDocumentStorageService {
463
469
  props["cacheEntryAge"] = undefined;
464
470
  }
465
471
  event.end({ ...props, method });
466
- return cachedSnapshot;
472
+ return retrievedSnapshot;
467
473
  },
468
- {end: true, cancel: "error"},
469
474
  );
470
475
 
471
- // Successful call, redirect future calls to getVersion only!
476
+ // Successful call, make network calls only
472
477
  this.firstVersionCall = false;
473
478
 
474
479
  this._snapshotSequenceNumber = odspSnapshotCacheValue.sequenceNumber;
@@ -503,10 +508,16 @@ export class OdspDocumentStorageService implements IDocumentStorageService {
503
508
  );
504
509
  const versionsResponse = response.content;
505
510
  if (!versionsResponse) {
506
- throwOdspNetworkError("getVersionsReturnedNoResponse", 400);
511
+ throw new NonRetryableError(
512
+ "getVersionsReturnedNoResponse",
513
+ "No response from /versions endpoint",
514
+ DriverErrorType.genericNetworkError);
507
515
  }
508
516
  if (!Array.isArray(versionsResponse.value)) {
509
- throwOdspNetworkError("getVersionsReturnedNonArrayResponse", 400);
517
+ throw new NonRetryableError(
518
+ "getVersionsReturnedNonArrayResponse",
519
+ "Incorrect response from /versions endpoint",
520
+ DriverErrorType.genericNetworkError);
510
521
  }
511
522
  return versionsResponse.value.map((version) => {
512
523
  // Parse the date from the message
@@ -678,19 +689,28 @@ export class OdspDocumentStorageService implements IDocumentStorageService {
678
689
 
679
690
  private checkSnapshotUrl() {
680
691
  if (!this.snapshotUrl) {
681
- throwOdspNetworkError("methodNotSupportedBecauseNoSnapshotUrlWasProvided", 400);
692
+ throw new NonRetryableError(
693
+ "noSnapshotUrlProvided",
694
+ "Method failed because no snapshot url was available",
695
+ DriverErrorType.genericError);
682
696
  }
683
697
  }
684
698
 
685
699
  private checkAttachmentPOSTUrl() {
686
700
  if (!this.attachmentPOSTUrl) {
687
- throwOdspNetworkError("methodNotSupportedBecauseNoAttachmentPOSTUrlWasProvided", 400);
701
+ throw new NonRetryableError(
702
+ "noAttachmentPOSTUrlProvided",
703
+ "Method failed because no attachment POST url was available",
704
+ DriverErrorType.genericError);
688
705
  }
689
706
  }
690
707
 
691
708
  private checkAttachmentGETUrl() {
692
709
  if (!this.attachmentGETUrl) {
693
- throwOdspNetworkError("methodNotSupportedBecauseNoAttachmentGETUrlWasProvided", 400);
710
+ throw new NonRetryableError(
711
+ "noAttachmentGETUrlWasProvided",
712
+ "Method failed because no attachment GET url was available",
713
+ DriverErrorType.genericError);
694
714
  }
695
715
  }
696
716
 
@@ -7,7 +7,7 @@ import { PromiseCache } from "@fluidframework/common-utils";
7
7
  import { IFluidCodeDetails, IRequest, isFluidPackage } from "@fluidframework/core-interfaces";
8
8
  import { IResolvedUrl, IUrlResolver } from "@fluidframework/driver-definitions";
9
9
  import { ITelemetryBaseLogger, ITelemetryLogger } from "@fluidframework/common-definitions";
10
- import { fetchTokenErrorCode, throwOdspNetworkError } from "@fluidframework/odsp-doclib-utils";
10
+ import { NonRetryableError } from "@fluidframework/driver-utils";
11
11
  import { PerformanceEvent } from "@fluidframework/telemetry-utils";
12
12
  import {
13
13
  IOdspResolvedUrl,
@@ -15,6 +15,7 @@ import {
15
15
  isTokenFromCache,
16
16
  OdspResourceTokenFetchOptions,
17
17
  TokenFetcher,
18
+ OdspErrorType,
18
19
  } from "@fluidframework/odsp-driver-definitions";
19
20
  import {
20
21
  getLocatorFromOdspUrl,
@@ -132,8 +133,9 @@ export class OdspDriverUrlResolverForShareLink implements IUrlResolver {
132
133
  // We need to remove the nav param if set by host when setting the sharelink as otherwise the shareLinkId
133
134
  // when redeeming the share link during the redeem fallback for trees latest call becomes greater than
134
135
  // the eligible length.
136
+ odspResolvedUrl.sharingLinkToRedeem = this.removeNavParam(request.url);
135
137
  odspResolvedUrl.shareLinkInfo = Object.assign(odspResolvedUrl.shareLinkInfo || {},
136
- {sharingLinkToRedeem: this.removeNavParam(request.url)});
138
+ {sharingLinkToRedeem: odspResolvedUrl.sharingLinkToRedeem});
137
139
  }
138
140
  if (odspResolvedUrl.itemId) {
139
141
  // Kick start the sharing link request if we don't have it already as a performance optimization.
@@ -161,7 +163,10 @@ export class OdspDriverUrlResolverForShareLink implements IUrlResolver {
161
163
  { eventName: "GetSharingLinkToken" },
162
164
  async (event) => tokenFetcher(options).then((tokenResponse) => {
163
165
  if (tokenResponse === null) {
164
- throwOdspNetworkError("shareLinkTokenIsNull", fetchTokenErrorCode);
166
+ throw new NonRetryableError(
167
+ "shareLinkTokenIsNull",
168
+ "Token callback returned null",
169
+ OdspErrorType.fetchTokenError);
165
170
  }
166
171
  event.end({ fromCache: isTokenFromCache(tokenResponse) });
167
172
  return tokenResponse;
package/src/odspError.ts CHANGED
@@ -11,9 +11,13 @@ import { IOdspSocketError } from "./contracts";
11
11
  */
12
12
  export function errorObjectFromSocketError(socketError: IOdspSocketError, handler: string) {
13
13
  const message = `OdspSocketError (${handler}): ${socketError.message}`;
14
- return createOdspNetworkError(
14
+ const error = createOdspNetworkError(
15
15
  `odspSocketError [${handler}]`,
16
16
  message,
17
17
  socketError.code,
18
18
  socketError.retryAfter);
19
+
20
+ error.addTelemetryProperties({ odspError: true, relayServiceError: true });
21
+
22
+ return error;
19
23
  }
@@ -9,7 +9,7 @@ import { ISummaryContext } from "@fluidframework/driver-definitions";
9
9
  import { getGitType } from "@fluidframework/protocol-base";
10
10
  import * as api from "@fluidframework/protocol-definitions";
11
11
  import { InstrumentedStorageTokenFetcher } from "@fluidframework/odsp-driver-definitions";
12
- import { PerformanceEvent } from "@fluidframework/telemetry-utils";
12
+ import { loggerToMonitoringContext, MonitoringContext, PerformanceEvent } from "@fluidframework/telemetry-utils";
13
13
  import {
14
14
  IOdspSummaryPayload,
15
15
  IWriteSummaryResponse,
@@ -24,23 +24,6 @@ import { getWithRetryForTokenRefresh } from "./odspUtils";
24
24
 
25
25
  /* eslint-disable max-len */
26
26
 
27
- // Gate that when flipped, instructs to mark unreferenced nodes as such in the summary sent to SPO.
28
- function gatesMarkUnreferencedNodes() {
29
- try {
30
- // Leave override for testing purposes
31
- if (typeof localStorage === "object" && localStorage !== null) {
32
- if (localStorage.FluidMarkUnreferencedNodes === "1") {
33
- return true;
34
- }
35
- if (localStorage.FluidMarkUnreferencedNodes === "0") {
36
- return false;
37
- }
38
- }
39
- } catch (e) {}
40
-
41
- return true;
42
- }
43
-
44
27
  /**
45
28
  * This class manages a summary upload. When it receives a call to upload summary, it converts the summary tree into
46
29
  * a snapshot tree and then uploads that to the server.
@@ -48,20 +31,22 @@ function gatesMarkUnreferencedNodes() {
48
31
  export class OdspSummaryUploadManager {
49
32
  // Last proposed handle of the uploaded app summary.
50
33
  private lastSummaryProposalHandle: string | undefined;
34
+ private readonly mc: MonitoringContext;
51
35
 
52
36
  constructor(
53
37
  private readonly snapshotUrl: string,
54
38
  private readonly getStorageToken: InstrumentedStorageTokenFetcher,
55
- private readonly logger: ITelemetryLogger,
39
+ logger: ITelemetryLogger,
56
40
  private readonly epochTracker: EpochTracker,
57
41
  ) {
42
+ this.mc = loggerToMonitoringContext(logger);
58
43
  }
59
44
 
60
45
  public async writeSummaryTree(tree: api.ISummaryTree, context: ISummaryContext) {
61
46
  // If the last proposed handle is not the proposed handle of the acked summary(could happen when the last summary get nacked),
62
47
  // then re-initialize the caches with the previous ones else just update the previous caches with the caches from acked summary.
63
48
  if (context.proposalHandle !== this.lastSummaryProposalHandle) {
64
- this.logger.sendTelemetryEvent({
49
+ this.mc.logger.sendTelemetryEvent({
65
50
  eventName: "LastSummaryProposedHandleMismatch",
66
51
  ackedSummaryProposedHandle: context.proposalHandle,
67
52
  lastSummaryProposalHandle: this.lastSummaryProposalHandle,
@@ -107,7 +92,7 @@ export class OdspSummaryUploadManager {
107
92
 
108
93
  const postBody = JSON.stringify(snapshot);
109
94
 
110
- return PerformanceEvent.timedExecAsync(this.logger,
95
+ return PerformanceEvent.timedExecAsync(this.mc.logger,
111
96
  {
112
97
  eventName: "uploadSummary",
113
98
  attempt: options.refresh ? 2 : 1,
@@ -145,7 +130,7 @@ export class OdspSummaryUploadManager {
145
130
  tree: api.ISummaryTree,
146
131
  rootNodeName: string,
147
132
  path: string = "",
148
- markUnreferencedNodes: boolean = gatesMarkUnreferencedNodes(),
133
+ markUnreferencedNodes: boolean = this.mc.config.getBoolean("Fluid.Driver.Odsp.MarkUnreferencedNodes") ?? true,
149
134
  ) {
150
135
  const snapshotTree: IOdspSummaryTree = {
151
136
  type: "tree",
package/src/odspUtils.ts CHANGED
@@ -5,19 +5,14 @@
5
5
 
6
6
  import { ITelemetryProperties, ITelemetryBaseLogger, ITelemetryLogger } from "@fluidframework/common-definitions";
7
7
  import { IResolvedUrl, DriverErrorType } from "@fluidframework/driver-definitions";
8
- import { isOnline, OnlineStatus } from "@fluidframework/driver-utils";
8
+ import { isOnline, OnlineStatus, RetryableError, NonRetryableError } from "@fluidframework/driver-utils";
9
9
  import { assert, performance } from "@fluidframework/common-utils";
10
10
  import { ISequencedDocumentMessage, ISnapshotTree } from "@fluidframework/protocol-definitions";
11
11
  import { ChildLogger, PerformanceEvent, wrapError } from "@fluidframework/telemetry-utils";
12
12
  import {
13
13
  fetchIncorrectResponse,
14
- offlineFetchFailureStatusCode,
15
- fetchFailureStatusCode,
16
- fetchTimeoutStatusCode,
17
14
  throwOdspNetworkError,
18
15
  getSPOAndGraphRequestIdsFromResponse,
19
- fetchTokenErrorCode,
20
- createOdspNetworkError,
21
16
  } from "@fluidframework/odsp-doclib-utils";
22
17
  import {
23
18
  IOdspResolvedUrl,
@@ -82,7 +77,7 @@ export async function getWithRetryForTokenRefresh<T>(get: (options: TokenFetchOp
82
77
  case DriverErrorType.authorizationError:
83
78
  return get({ ...options, claims: e.claims, tenantId: e.tenantId });
84
79
 
85
- case DriverErrorType.incorrectServerResponse: // fetchIncorrectResponse - some error on the wire, retry once
80
+ case DriverErrorType.incorrectServerResponse: // some error on the wire, retry once
86
81
  case OdspErrorType.fetchTokenError: // If the token was null, then retry once.
87
82
  return get(options);
88
83
 
@@ -107,7 +102,10 @@ export async function fetchHelper(
107
102
  const response = fetchResponse as any as Response;
108
103
  // Let's assume we can retry.
109
104
  if (!response) {
110
- throwOdspNetworkError("odspFetchErrorNoResponse", fetchIncorrectResponse);
105
+ throw new NonRetryableError(
106
+ "odspFetchErrorNoResponse",
107
+ "No response from fetch call",
108
+ DriverErrorType.incorrectServerResponse);
111
109
  }
112
110
  if (!response.ok || response.status < 200 || response.status >= 300) {
113
111
  throwOdspNetworkError(
@@ -130,11 +128,13 @@ export async function fetchHelper(
130
128
  if (errorText === "TypeError: Failed to fetch") {
131
129
  online = OnlineStatus.Offline;
132
130
  }
131
+ // This error is thrown by fetch() when AbortSignal is provided and it gets cancelled
133
132
  if (error.name === "AbortError") {
134
- throwOdspNetworkError("timeoutDuringFetch", fetchTimeoutStatusCode);
133
+ throw new RetryableError("fetchAbort", "Fetch Timeout (AbortError)", OdspErrorType.fetchTimeout);
135
134
  }
135
+ // TCP/IP timeout
136
136
  if (errorText.indexOf("ETIMEDOUT") !== -1) {
137
- throwOdspNetworkError("timeoutDuringFetch(ETIMEDOUT)", fetchTimeoutStatusCode);
137
+ throw new RetryableError("fetchETimedout", "Fetch Timeout (ETIMEDOUT)", OdspErrorType.fetchTimeout);
138
138
  }
139
139
 
140
140
  //
@@ -142,11 +142,11 @@ export async function fetchHelper(
142
142
  // It could container PII, like URI in message itself, or token in properties.
143
143
  // It is also non-serializable object due to circular references.
144
144
  //
145
- const failureCode = online === OnlineStatus.Offline ? offlineFetchFailureStatusCode : fetchFailureStatusCode;
146
- throwOdspNetworkError(
147
- `odspFetchThrewError [${failureCode}]`,
148
- failureCode,
149
- );
145
+ if (online === OnlineStatus.Offline) {
146
+ throw new RetryableError("OdspFetchOffline", `Offline: ${errorText}`, DriverErrorType.offlineError);
147
+ } else {
148
+ throw new RetryableError("OdspFetchError", `Fetch error: ${errorText}`, DriverErrorType.fetchFailure);
149
+ }
150
150
  });
151
151
  }
152
152
 
@@ -181,30 +181,31 @@ export async function fetchAndParseAsJSONHelper<T>(
181
181
  requestInit: RequestInit | undefined,
182
182
  ): Promise<IOdspResponse<T>> {
183
183
  const { content, headers, commonSpoHeaders, duration } = await fetchHelper(requestInfo, requestInit);
184
- // JSON.parse() can fail and message (that goes into telemetry) would container full request URI, including
185
- // tokens... It fails for me with "Unexpected end of JSON input" quite often - an attempt to download big file
186
- // (many ops) almost always ends up with this error - I'd guess 1% of op request end up here... It always
187
- // succeeds on retry.
184
+ let text: string | undefined;
188
185
  try {
189
- const text = await content.text();
190
-
191
- commonSpoHeaders.bodySize = text.length;
192
- const res = {
193
- headers,
194
- content: JSON.parse(text),
195
- commonSpoHeaders,
196
- duration,
197
- };
198
- return res;
186
+ text = await content.text();
199
187
  } catch (e) {
188
+ // JSON.parse() can fail and message would container full request URI, including
189
+ // tokens... It fails for me with "Unexpected end of JSON input" quite often - an attempt to download big file
190
+ // (many ops) almost always ends up with this error - I'd guess 1% of op request end up here... It always
191
+ // succeeds on retry.
192
+ // So do not log error object itself.
200
193
  throwOdspNetworkError(
201
194
  "errorWhileParsingFetchResponse",
202
195
  fetchIncorrectResponse,
203
- content,
204
- undefined,
205
- { error: Object(e) },
196
+ content, // response
197
+ text,
206
198
  );
207
199
  }
200
+
201
+ commonSpoHeaders.bodySize = text.length;
202
+ const res = {
203
+ headers,
204
+ content: JSON.parse(text),
205
+ commonSpoHeaders,
206
+ duration,
207
+ };
208
+ return res;
208
209
  }
209
210
 
210
211
  export interface INewFileInfo {
@@ -293,13 +294,21 @@ export function toInstrumentedOdspTokenFetcher(
293
294
  event.end({ fromCache: isTokenFromCache(tokenResponse), isNull: token === null });
294
295
  }
295
296
  if (token === null && throwOnNullToken) {
296
- throwOdspNetworkError("tokenIsNull", fetchTokenErrorCode, undefined, undefined, { method: name });
297
+ throw new NonRetryableError(
298
+ "storageTokenIsNull",
299
+ `Token is null for ${name} call`,
300
+ OdspErrorType.fetchTokenError,
301
+ { method: name });
297
302
  }
298
303
  return token;
299
304
  }, (error) => {
300
305
  const tokenError = wrapError(
301
306
  error,
302
- (errorMessage) => createOdspNetworkError("tokenFetcherFailed", errorMessage, fetchTokenErrorCode));
307
+ (errorMessage) => new NonRetryableError(
308
+ "tokenFetcherFailed",
309
+ errorMessage,
310
+ OdspErrorType.fetchTokenError,
311
+ { method: name }));
303
312
  throw tokenError;
304
313
  }),
305
314
  { cancel: "generic" });
@@ -6,4 +6,4 @@
6
6
  */
7
7
 
8
8
  export const pkgName = "@fluidframework/odsp-driver";
9
- export const pkgVersion = "0.52.1";
9
+ export const pkgVersion = "0.54.0";
package/src/vroom.ts CHANGED
@@ -88,11 +88,16 @@ export async function fetchJoinSession(
88
88
  logger,
89
89
  );
90
90
 
91
+ const socketUrl = response.content.deltaStreamSocketUrl;
92
+ // expecting socketUrl to be something like https://{hostName}/...
93
+ const webSocketHostName = socketUrl.split("/")[2];
94
+
91
95
  // TODO SPO-specific telemetry
92
96
  event.end({
93
97
  ...response.commonSpoHeaders,
94
98
  // pushV2 websocket urls will contain pushf
95
- pushv2: response.content.deltaStreamSocketUrl.includes("pushf"),
99
+ pushv2: socketUrl.includes("pushf"),
100
+ webSocketHostName,
96
101
  });
97
102
 
98
103
  if (response.content.runtimeTenantId && !response.content.tenantId) {