@fluidframework/odsp-driver 0.47.0 → 0.48.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 (72) hide show
  1. package/dist/createFile.js +3 -3
  2. package/dist/createFile.js.map +1 -1
  3. package/dist/epochTracker.js +1 -1
  4. package/dist/epochTracker.js.map +1 -1
  5. package/dist/odspDeltaStorageService.d.ts +1 -0
  6. package/dist/odspDeltaStorageService.d.ts.map +1 -1
  7. package/dist/odspDeltaStorageService.js +26 -1
  8. package/dist/odspDeltaStorageService.js.map +1 -1
  9. package/dist/odspDocumentDeltaConnection.d.ts.map +1 -1
  10. package/dist/odspDocumentDeltaConnection.js +11 -10
  11. package/dist/odspDocumentDeltaConnection.js.map +1 -1
  12. package/dist/odspDocumentService.js +4 -4
  13. package/dist/odspDocumentService.js.map +1 -1
  14. package/dist/odspDocumentStorageManager.js +5 -5
  15. package/dist/odspDocumentStorageManager.js.map +1 -1
  16. package/dist/odspDriverUrlResolverForShareLink.js +1 -1
  17. package/dist/odspDriverUrlResolverForShareLink.js.map +1 -1
  18. package/dist/odspUtils.d.ts.map +1 -1
  19. package/dist/odspUtils.js +6 -6
  20. package/dist/odspUtils.js.map +1 -1
  21. package/dist/opsCaching.d.ts +1 -0
  22. package/dist/opsCaching.d.ts.map +1 -1
  23. package/dist/opsCaching.js +12 -9
  24. package/dist/opsCaching.js.map +1 -1
  25. package/dist/packageVersion.d.ts +1 -1
  26. package/dist/packageVersion.js +1 -1
  27. package/dist/packageVersion.js.map +1 -1
  28. package/dist/vroom.d.ts.map +1 -1
  29. package/dist/vroom.js +3 -0
  30. package/dist/vroom.js.map +1 -1
  31. package/lib/createFile.js +3 -3
  32. package/lib/createFile.js.map +1 -1
  33. package/lib/epochTracker.js +1 -1
  34. package/lib/epochTracker.js.map +1 -1
  35. package/lib/odspDeltaStorageService.d.ts +1 -0
  36. package/lib/odspDeltaStorageService.d.ts.map +1 -1
  37. package/lib/odspDeltaStorageService.js +26 -1
  38. package/lib/odspDeltaStorageService.js.map +1 -1
  39. package/lib/odspDocumentDeltaConnection.d.ts.map +1 -1
  40. package/lib/odspDocumentDeltaConnection.js +11 -10
  41. package/lib/odspDocumentDeltaConnection.js.map +1 -1
  42. package/lib/odspDocumentService.js +4 -4
  43. package/lib/odspDocumentService.js.map +1 -1
  44. package/lib/odspDocumentStorageManager.js +5 -5
  45. package/lib/odspDocumentStorageManager.js.map +1 -1
  46. package/lib/odspDriverUrlResolverForShareLink.js +1 -1
  47. package/lib/odspDriverUrlResolverForShareLink.js.map +1 -1
  48. package/lib/odspUtils.d.ts.map +1 -1
  49. package/lib/odspUtils.js +6 -6
  50. package/lib/odspUtils.js.map +1 -1
  51. package/lib/opsCaching.d.ts +1 -0
  52. package/lib/opsCaching.d.ts.map +1 -1
  53. package/lib/opsCaching.js +12 -9
  54. package/lib/opsCaching.js.map +1 -1
  55. package/lib/packageVersion.d.ts +1 -1
  56. package/lib/packageVersion.js +1 -1
  57. package/lib/packageVersion.js.map +1 -1
  58. package/lib/vroom.d.ts.map +1 -1
  59. package/lib/vroom.js +3 -0
  60. package/lib/vroom.js.map +1 -1
  61. package/package.json +9 -9
  62. package/src/createFile.ts +3 -3
  63. package/src/epochTracker.ts +1 -1
  64. package/src/odspDeltaStorageService.ts +60 -33
  65. package/src/odspDocumentDeltaConnection.ts +13 -5
  66. package/src/odspDocumentService.ts +3 -3
  67. package/src/odspDocumentStorageManager.ts +5 -5
  68. package/src/odspDriverUrlResolverForShareLink.ts +1 -1
  69. package/src/odspUtils.ts +12 -6
  70. package/src/opsCaching.ts +14 -9
  71. package/src/packageVersion.ts +1 -1
  72. package/src/vroom.ts +3 -0
@@ -1 +1 @@
1
- {"version":3,"file":"opsCaching.js","sourceRoot":"","sources":["../src/opsCaching.ts"],"names":[],"mappings":"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","sourcesContent":["/*!\n * Copyright (c) Microsoft Corporation and contributors. All rights reserved.\n * Licensed under the MIT License.\n */\n\nimport { ITelemetryLogger } from \"@fluidframework/common-definitions\";\nimport { performance } from \"@fluidframework/common-utils\";\n\n// ISequencedDocumentMessage\nexport interface IMessage {\n sequenceNumber: number;\n}\n\nexport type CacheEntry = (IMessage | undefined)[];\n\nexport interface IBatch {\n remainingSlots: number;\n batchData: CacheEntry;\n /**\n * Tells if this batch is dirty, i.e. it contains ops that were not flushed to cache\n */\n dirty: boolean;\n}\n\nexport interface ICache {\n write(batchNumber: string, data: string): Promise<void>;\n read(batchNumber: string): Promise<string | undefined>;\n remove(): void;\n}\n\nexport class OpsCache {\n private readonly batches: Map<number, null | IBatch> = new Map();\n private timer: ReturnType<typeof setTimeout> | undefined;\n\n constructor(\n startingSequenceNumber: number,\n private readonly logger: ITelemetryLogger,\n private readonly cache: ICache,\n private readonly batchSize: number,\n private readonly timerGranularity,\n private totalOpsToCache,\n ) {\n /** initial batch is a special case because it will never be full - all ops prior (inclusive) to\n * startingSequenceNumber are never going to show up (undefined)\n */\n const remainingSlots = this.batchSize - this.getPositionInBatchArray(startingSequenceNumber) - 1;\n if (remainingSlots !== 0) {\n this.batches.set(this.getBatchNumber(startingSequenceNumber), {\n remainingSlots,\n batchData : this.initializeNewBatchDataArray(),\n dirty: false,\n });\n }\n }\n\n public dispose() {\n this.batches.clear();\n if (this.timer !== undefined) {\n clearTimeout(this.timer);\n this.timer = undefined;\n }\n }\n\n public flushOps() {\n for (const [key, value] of this.batches) {\n if (value === null || !value.dirty) {\n continue;\n }\n value.dirty = false;\n this.write(key, value);\n }\n }\n\n public addOps(ops: IMessage[]) {\n if (this.totalOpsToCache <= 0) {\n return;\n }\n\n for (const op of ops) {\n const batchNumber = this.getBatchNumber(op.sequenceNumber);\n const positionInBatch = this.getPositionInBatchArray(op.sequenceNumber);\n\n let currentBatch = this.batches.get(batchNumber);\n\n if (currentBatch === undefined) {\n currentBatch = {\n remainingSlots: this.batchSize - 1,\n batchData: this.initializeNewBatchDataArray(),\n dirty: true,\n };\n currentBatch.batchData[positionInBatch] = op;\n this.batches.set(batchNumber, currentBatch);\n } else if (currentBatch !== null && currentBatch.batchData[positionInBatch] === undefined) {\n currentBatch.batchData[positionInBatch] = op;\n currentBatch.remainingSlots--;\n currentBatch.dirty = true;\n } else {\n // Either batch was flushed or this op was already there - nothing to do!\n return;\n }\n\n if (currentBatch.remainingSlots === 0) {\n // batch is full, flush to cache\n this.write(batchNumber, currentBatch);\n this.batches.set(batchNumber, null);\n } else {\n this.scheduleTimer();\n }\n\n this.totalOpsToCache--;\n if (this.totalOpsToCache === 0) {\n this.logger.sendPerformanceEvent({ eventName: \"CacheOpsLimitHit\"});\n this.cache.remove();\n this.dispose();\n break;\n }\n }\n }\n\n public async get(from: number, to?: number): Promise<IMessage[]> {\n const messages: IMessage[] = [];\n let batchNumber = this.getBatchNumber(from + 1);\n const start = performance.now();\n // eslint-disable-next-line no-constant-condition\n while (true) {\n const res = await this.cache.read(`${this.batchSize}_${batchNumber}`);\n if (res === undefined) {\n break;\n }\n const result: CacheEntry = JSON.parse(res);\n for (const op of result) {\n // Note that we write out undefined, but due to JSON.stringify, it turns into null!\n if (op) {\n if (to !== undefined && op.sequenceNumber >= to) {\n break;\n }\n if (messages.length === 0) {\n if (op.sequenceNumber > from + 1) {\n break;\n } else if (op.sequenceNumber <= from) {\n continue;\n }\n }\n messages.push(op);\n } else if (messages.length !== 0) {\n break;\n }\n }\n\n batchNumber++;\n }\n\n const duration = performance.now() - start;\n if (messages.length > 0 || duration > 1000) {\n this.logger.sendPerformanceEvent({\n eventName: \"CacheOpsUsed\",\n from,\n to,\n length: messages.length,\n duration,\n });\n }\n return messages;\n }\n\n protected write(batchNumber: number, payload: IBatch) {\n // Errors are caught and logged by PersistedCacheWithErrorHandling that sits\n // in the adapter chain of cache adapters\n this.cache.write(`${this.batchSize}_${batchNumber}`, JSON.stringify(payload.batchData)).catch(() => {\n this.totalOpsToCache = 0;\n });\n }\n\n protected scheduleTimer() {\n if (!this.timer && this.timerGranularity > 0) {\n this.timer = setTimeout(() => {\n this.timer = undefined;\n this.flushOps();\n }, this.timerGranularity);\n }\n }\n\n private getBatchNumber(sequenceNumber: number) {\n return Math.floor(sequenceNumber / this.batchSize);\n }\n\n private getPositionInBatchArray(sequenceNumber: number) {\n return sequenceNumber % this.batchSize;\n }\n\n private initializeNewBatchDataArray() {\n const tempArray: IMessage[] = [];\n tempArray.length = this.batchSize; // fill with empty, undefined elements\n return tempArray;\n }\n}\n"]}
1
+ {"version":3,"file":"opsCaching.js","sourceRoot":"","sources":["../src/opsCaching.ts"],"names":[],"mappings":"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","sourcesContent":["/*!\n * Copyright (c) Microsoft Corporation and contributors. All rights reserved.\n * Licensed under the MIT License.\n */\n\nimport { ITelemetryLogger } from \"@fluidframework/common-definitions\";\nimport { performance } from \"@fluidframework/common-utils\";\n\n// ISequencedDocumentMessage\nexport interface IMessage {\n sequenceNumber: number;\n}\n\nexport type CacheEntry = (IMessage | undefined)[];\n\nexport interface IBatch {\n remainingSlots: number;\n batchData: CacheEntry;\n /**\n * Tells if this batch is dirty, i.e. it contains ops that were not flushed to cache\n */\n dirty: boolean;\n}\n\nexport interface ICache {\n write(batchNumber: string, data: string): Promise<void>;\n read(batchNumber: string): Promise<string | undefined>;\n remove(): void;\n}\n\nexport class OpsCache {\n private readonly batches: Map<number, null | IBatch> = new Map();\n private timer: ReturnType<typeof setTimeout> | undefined;\n\n constructor(\n startingSequenceNumber: number,\n private readonly logger: ITelemetryLogger,\n private readonly cache: ICache,\n private readonly batchSize: number,\n private readonly timerGranularity,\n private totalOpsToCache,\n ) {\n /** initial batch is a special case because it will never be full - all ops prior (inclusive) to\n * startingSequenceNumber are never going to show up (undefined)\n */\n const remainingSlots = this.batchSize - this.getPositionInBatchArray(startingSequenceNumber) - 1;\n if (remainingSlots !== 0) {\n this.batches.set(this.getBatchNumber(startingSequenceNumber), {\n remainingSlots,\n batchData : this.initializeNewBatchDataArray(),\n dirty: false,\n });\n }\n }\n\n public dispose() {\n this.batches.clear();\n if (this.timer !== undefined) {\n clearTimeout(this.timer);\n this.timer = undefined;\n }\n }\n\n public flushOps() {\n for (const [key, value] of this.batches) {\n if (value === null || !value.dirty) {\n continue;\n }\n value.dirty = false;\n this.write(key, value);\n }\n }\n\n public addOps(ops: IMessage[]) {\n if (this.totalOpsToCache <= 0) {\n return;\n }\n\n for (const op of ops) {\n const batchNumber = this.getBatchNumber(op.sequenceNumber);\n const positionInBatch = this.getPositionInBatchArray(op.sequenceNumber);\n\n let currentBatch = this.batches.get(batchNumber);\n\n if (currentBatch === undefined) {\n currentBatch = {\n remainingSlots: this.batchSize - 1,\n batchData: this.initializeNewBatchDataArray(),\n dirty: true,\n };\n currentBatch.batchData[positionInBatch] = op;\n this.batches.set(batchNumber, currentBatch);\n } else if (currentBatch !== null && currentBatch.batchData[positionInBatch] === undefined) {\n currentBatch.batchData[positionInBatch] = op;\n currentBatch.remainingSlots--;\n currentBatch.dirty = true;\n } else {\n // Either batch was flushed or this op was already there - nothing to do!\n return;\n }\n\n if (currentBatch.remainingSlots === 0) {\n // batch is full, flush to cache\n this.write(batchNumber, currentBatch);\n this.batches.set(batchNumber, null);\n } else {\n this.scheduleTimer();\n }\n\n this.totalOpsToCache--;\n if (this.totalOpsToCache === 0) {\n this.logger.sendPerformanceEvent({ eventName: \"CacheOpsLimitHit\"});\n this.cache.remove();\n this.dispose();\n break;\n }\n }\n }\n\n private async getCore(from: number, to?: number): Promise<IMessage[]> {\n const messages: IMessage[] = [];\n let batchNumber = this.getBatchNumber(from);\n // eslint-disable-next-line no-constant-condition\n while (true) {\n const res = await this.cache.read(`${this.batchSize}_${batchNumber}`);\n if (res === undefined) {\n return messages;\n }\n const result: CacheEntry = JSON.parse(res);\n for (const op of result) {\n // Note that we write out undefined, but due to JSON.stringify, it turns into null!\n if (op) {\n if (to !== undefined && op.sequenceNumber >= to) {\n return messages;\n }\n if (messages.length === 0) {\n if (op.sequenceNumber > from) {\n return messages;\n } else if (op.sequenceNumber < from) {\n continue;\n }\n }\n messages.push(op);\n } else if (messages.length !== 0) {\n return messages;\n }\n }\n\n batchNumber++;\n }\n }\n\n public async get(from: number, to?: number): Promise<IMessage[]> {\n const start = performance.now();\n\n const messages = await this.getCore(from, to);\n\n const duration = performance.now() - start;\n if (messages.length > 0 || duration > 1000) {\n this.logger.sendPerformanceEvent({\n eventName: \"CacheOpsUsed\",\n from,\n to,\n length: messages.length,\n duration,\n });\n }\n return messages;\n }\n\n protected write(batchNumber: number, payload: IBatch) {\n // Errors are caught and logged by PersistedCacheWithErrorHandling that sits\n // in the adapter chain of cache adapters\n this.cache.write(`${this.batchSize}_${batchNumber}`, JSON.stringify(payload.batchData)).catch(() => {\n this.totalOpsToCache = 0;\n });\n }\n\n protected scheduleTimer() {\n if (!this.timer && this.timerGranularity > 0) {\n this.timer = setTimeout(() => {\n this.timer = undefined;\n this.flushOps();\n }, this.timerGranularity);\n }\n }\n\n private getBatchNumber(sequenceNumber: number) {\n return Math.floor(sequenceNumber / this.batchSize);\n }\n\n private getPositionInBatchArray(sequenceNumber: number) {\n return sequenceNumber % this.batchSize;\n }\n\n private initializeNewBatchDataArray() {\n const tempArray: IMessage[] = [];\n tempArray.length = this.batchSize; // fill with empty, undefined elements\n return tempArray;\n }\n}\n"]}
@@ -5,5 +5,5 @@
5
5
  * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY
6
6
  */
7
7
  export declare const pkgName = "@fluidframework/odsp-driver";
8
- export declare const pkgVersion = "0.47.0";
8
+ export declare const pkgVersion = "0.48.0";
9
9
  //# sourceMappingURL=packageVersion.d.ts.map
@@ -5,5 +5,5 @@
5
5
  * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY
6
6
  */
7
7
  export const pkgName = "@fluidframework/odsp-driver";
8
- export const pkgVersion = "0.47.0";
8
+ export const pkgVersion = "0.48.0";
9
9
  //# sourceMappingURL=packageVersion.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"packageVersion.js","sourceRoot":"","sources":["../src/packageVersion.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,MAAM,CAAC,MAAM,OAAO,GAAG,6BAA6B,CAAC;AACrD,MAAM,CAAC,MAAM,UAAU,GAAG,QAAQ,CAAC","sourcesContent":["/*!\n * Copyright (c) Microsoft Corporation and contributors. All rights reserved.\n * Licensed under the MIT License.\n *\n * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY\n */\n\nexport const pkgName = \"@fluidframework/odsp-driver\";\nexport const pkgVersion = \"0.47.0\";\n"]}
1
+ {"version":3,"file":"packageVersion.js","sourceRoot":"","sources":["../src/packageVersion.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,MAAM,CAAC,MAAM,OAAO,GAAG,6BAA6B,CAAC;AACrD,MAAM,CAAC,MAAM,UAAU,GAAG,QAAQ,CAAC","sourcesContent":["/*!\n * Copyright (c) Microsoft Corporation and contributors. All rights reserved.\n * Licensed under the MIT License.\n *\n * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY\n */\n\nexport const pkgName = \"@fluidframework/odsp-driver\";\nexport const pkgVersion = \"0.48.0\";\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"vroom.d.ts","sourceRoot":"","sources":["../src/vroom.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,gBAAgB,EAAE,MAAM,oCAAoC,CAAC;AAEtE,OAAO,EAAE,+BAA+B,EAAE,aAAa,EAAE,MAAM,yCAAyC,CAAC;AACzG,OAAO,EAAE,uBAAuB,EAAE,MAAM,aAAa,CAAC;AAGtD,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAQ9C;;;;;;;;;;;;;;GAcG;AACH,wBAAsB,gBAAgB,CAClC,QAAQ,EAAE,aAAa,EACvB,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,gBAAgB,EACxB,eAAe,EAAE,+BAA+B,EAChD,YAAY,EAAE,YAAY,EAC1B,kBAAkB,EAAE,OAAO,EAC3B,gBAAgB,CAAC,EAAE,MAAM,GAC1B,OAAO,CAAC,uBAAuB,CAAC,CA2DlC"}
1
+ {"version":3,"file":"vroom.d.ts","sourceRoot":"","sources":["../src/vroom.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,gBAAgB,EAAE,MAAM,oCAAoC,CAAC;AAEtE,OAAO,EAAE,+BAA+B,EAAE,aAAa,EAAE,MAAM,yCAAyC,CAAC;AACzG,OAAO,EAAE,uBAAuB,EAAE,MAAM,aAAa,CAAC;AAGtD,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAQ9C;;;;;;;;;;;;;;GAcG;AACH,wBAAsB,gBAAgB,CAClC,QAAQ,EAAE,aAAa,EACvB,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,gBAAgB,EACxB,eAAe,EAAE,+BAA+B,EAChD,YAAY,EAAE,YAAY,EAC1B,kBAAkB,EAAE,OAAO,EAC3B,gBAAgB,CAAC,EAAE,MAAM,GAC1B,OAAO,CAAC,uBAAuB,CAAC,CA8DlC"}
package/lib/vroom.js CHANGED
@@ -45,6 +45,9 @@ export async function fetchJoinSession(urlParts, path, method, logger, getStorag
45
45
  if (guestDisplayName) {
46
46
  body.guestDisplayName = guestDisplayName;
47
47
  }
48
+ // IMPORTANT: Must set content-type header explicitly to application/json when request has body.
49
+ // By default, request will use text/plain as content-type and will be rejected by backend.
50
+ headers["Content-Type"] = "application/json";
48
51
  }
49
52
  const response = await runWithRetry(async () => epochTracker.fetchAndParseAsJSON(`${getApiRoot(siteOrigin)}/drives/${urlParts.driveId}/items/${urlParts.itemId}/${path}?${queryParams}`, { method, headers, body: body ? JSON.stringify(body) : undefined }, "joinSession"), "joinSession", logger);
50
53
  // TODO SPO-specific telemetry
package/lib/vroom.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"vroom.js","sourceRoot":"","sources":["../src/vroom.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,OAAO,EAAE,gBAAgB,EAAE,MAAM,iCAAiC,CAAC;AAGnE,OAAO,EAAE,2BAA2B,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AACrE,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAE7C,OAAO,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAO5C;;;;;;;;;;;;;;GAcG;AACH,MAAM,CAAC,KAAK,UAAU,gBAAgB,CAClC,QAAuB,EACvB,IAAY,EACZ,MAAc,EACd,MAAwB,EACxB,eAAgD,EAChD,YAA0B,EAC1B,kBAA2B,EAC3B,gBAAyB;IAEzB,OAAO,2BAA2B,CAAC,KAAK,EAAE,OAAO,EAAE,EAAE;QACjD,MAAM,KAAK,GAAG,MAAM,eAAe,CAAC,OAAO,EAAE,aAAa,CAAC,CAAC;QAE5D,MAAM,UAAU,GAAG,OAAO,CAAC,OAAO;YAC9B,CAAC,CAAC,EAAE,SAAS,EAAE,CAAC,CAAC,OAAO,CAAC,MAAM,EAAE,WAAW,EAAE,CAAC,CAAC,OAAO,CAAC,QAAQ,EAAE;YAClE,CAAC,CAAC,EAAE,CAAC;QACT,OAAO,gBAAgB,CAAC,cAAc,CAClC,MAAM,kBACF,SAAS,EAAE,aAAa,EACxB,QAAQ,EAAE,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAC9B,UAAU,GAEjB,KAAK,EAAE,KAAK,EAAE,EAAE;YACZ,kDAAkD;YAClD,MAAM,UAAU,GAAG,SAAS,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC;YAC/C,IAAI,WAAW,GAAG,gBAAgB,KAAK,EAAE,CAAC;YAC1C,IAAI,OAAO,GAAG,EAAE,CAAC;YACjB,IAAI,WAAW,CAAC,MAAM,GAAG,IAAI,EAAE;gBAC3B,WAAW,GAAG,EAAE,CAAC;gBACjB,OAAO,GAAG,EAAE,aAAa,EAAE,UAAU,KAAK,EAAE,EAAE,CAAC;aAClD;YACD,IAAI,IAAkC,CAAC;YACvC,IAAI,kBAAkB,IAAI,gBAAgB,EAAE;gBACxC,IAAI,GAAG,EAAE,CAAC;gBACV,IAAI,kBAAkB,EAAE;oBACpB,IAAI,CAAC,kBAAkB,GAAG,IAAI,CAAC;iBAClC;gBACD,IAAI,gBAAgB,EAAE;oBAClB,IAAI,CAAC,gBAAgB,GAAG,gBAAgB,CAAC;iBAC5C;aACJ;YAED,MAAM,QAAQ,GAAG,MAAM,YAAY,CAC/B,KAAK,IAAI,EAAE,CAAC,YAAY,CAAC,mBAAmB,CACxC,GAAG,UAAU,CAAC,UAAU,CAAC,WACrB,QAAQ,CAAC,OACb,UAAU,QAAQ,CAAC,MAAM,IAAI,IAAI,IAAI,WAAW,EAAE,EAClD,EAAE,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,SAAS,EAAE,EAClE,aAAa,CAChB,EACD,aAAa,EACb,MAAM,CACT,CAAC;YAEF,8BAA8B;YAC9B,KAAK,CAAC,GAAG,iCACF,QAAQ,CAAC,gBAAgB;gBAC5B,2CAA2C;gBAC3C,MAAM,EAAE,QAAQ,CAAC,OAAO,CAAC,oBAAoB,CAAC,QAAQ,CAAC,OAAO,CAAC,IACjE,CAAC;YAEH,IAAI,QAAQ,CAAC,OAAO,CAAC,eAAe,IAAI,CAAC,QAAQ,CAAC,OAAO,CAAC,QAAQ,EAAE;gBAChE,QAAQ,CAAC,OAAO,CAAC,QAAQ,GAAG,QAAQ,CAAC,OAAO,CAAC,eAAe,CAAC;aAChE;YAED,OAAO,QAAQ,CAAC,OAAO,CAAC;QAC5B,CAAC,CAAC,CAAC;IACX,CAAC,CAAC,CAAC;AACP,CAAC","sourcesContent":["/*!\n * Copyright (c) Microsoft Corporation and contributors. All rights reserved.\n * Licensed under the MIT License.\n */\n\nimport { ITelemetryLogger } from \"@fluidframework/common-definitions\";\nimport { PerformanceEvent } from \"@fluidframework/telemetry-utils\";\nimport { InstrumentedStorageTokenFetcher, IOdspUrlParts } from \"@fluidframework/odsp-driver-definitions\";\nimport { ISocketStorageDiscovery } from \"./contracts\";\nimport { getWithRetryForTokenRefresh, getOrigin } from \"./odspUtils\";\nimport { getApiRoot } from \"./odspUrlHelper\";\nimport { EpochTracker } from \"./epochTracker\";\nimport { runWithRetry } from \"./retryUtils\";\n\ninterface IJoinSessionBody {\n requestSocketToken?: boolean;\n guestDisplayName?: string;\n}\n\n/**\n * Makes join session call on SPO to get information about the web socket for a document\n * @param driveId - The SPO drive id that this request should be made against\n * @param itemId -The SPO item id that this request should be made against\n * @param siteUrl - The SPO site that this request should be made against\n * @param path - The API path that is relevant to this request\n * @param method - The type of request, such as GET or POST\n * @param logger - A logger to use for this request\n * @param getStorageToken - A function that is able to provide the access token for this request\n * @param epochTracker - fetch wrapper which incorporates epoch logic around joinSession call\n * @param requestSocketToken - flag indicating whether joinSession is expected to return access token\n * which is used when establishing websocket connection with collab session backend service.\n * @param guestDisplayName - display name used to identify guest user joining a session.\n * This is optional and used only when collab session is being joined via invite.\n */\nexport async function fetchJoinSession(\n urlParts: IOdspUrlParts,\n path: string,\n method: string,\n logger: ITelemetryLogger,\n getStorageToken: InstrumentedStorageTokenFetcher,\n epochTracker: EpochTracker,\n requestSocketToken: boolean,\n guestDisplayName?: string,\n): Promise<ISocketStorageDiscovery> {\n return getWithRetryForTokenRefresh(async (options) => {\n const token = await getStorageToken(options, \"JoinSession\");\n\n const extraProps = options.refresh\n ? { hasClaims: !!options.claims, hasTenantId: !!options.tenantId }\n : {};\n return PerformanceEvent.timedExecAsync(\n logger, {\n eventName: \"JoinSession\",\n attempts: options.refresh ? 2 : 1,\n ...extraProps,\n },\n async (event) => {\n // TODO Extract the auth header-vs-query logic out\n const siteOrigin = getOrigin(urlParts.siteUrl);\n let queryParams = `access_token=${token}`;\n let headers = {};\n if (queryParams.length > 2048) {\n queryParams = \"\";\n headers = { Authorization: `Bearer ${token}` };\n }\n let body: IJoinSessionBody | undefined;\n if (requestSocketToken || guestDisplayName) {\n body = {};\n if (requestSocketToken) {\n body.requestSocketToken = true;\n }\n if (guestDisplayName) {\n body.guestDisplayName = guestDisplayName;\n }\n }\n\n const response = await runWithRetry(\n async () => epochTracker.fetchAndParseAsJSON<ISocketStorageDiscovery>(\n `${getApiRoot(siteOrigin)}/drives/${\n urlParts.driveId\n }/items/${urlParts.itemId}/${path}?${queryParams}`,\n { method, headers, body: body ? JSON.stringify(body) : undefined },\n \"joinSession\",\n ),\n \"joinSession\",\n logger,\n );\n\n // TODO SPO-specific telemetry\n event.end({\n ...response.commonSpoHeaders,\n // pushV2 websocket urls will contain pushf\n pushv2: response.content.deltaStreamSocketUrl.includes(\"pushf\"),\n });\n\n if (response.content.runtimeTenantId && !response.content.tenantId) {\n response.content.tenantId = response.content.runtimeTenantId;\n }\n\n return response.content;\n });\n });\n}\n"]}
1
+ {"version":3,"file":"vroom.js","sourceRoot":"","sources":["../src/vroom.ts"],"names":[],"mappings":"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","sourcesContent":["/*!\n * Copyright (c) Microsoft Corporation and contributors. All rights reserved.\n * Licensed under the MIT License.\n */\n\nimport { ITelemetryLogger } from \"@fluidframework/common-definitions\";\nimport { PerformanceEvent } from \"@fluidframework/telemetry-utils\";\nimport { InstrumentedStorageTokenFetcher, IOdspUrlParts } from \"@fluidframework/odsp-driver-definitions\";\nimport { ISocketStorageDiscovery } from \"./contracts\";\nimport { getWithRetryForTokenRefresh, getOrigin } from \"./odspUtils\";\nimport { getApiRoot } from \"./odspUrlHelper\";\nimport { EpochTracker } from \"./epochTracker\";\nimport { runWithRetry } from \"./retryUtils\";\n\ninterface IJoinSessionBody {\n requestSocketToken?: boolean;\n guestDisplayName?: string;\n}\n\n/**\n * Makes join session call on SPO to get information about the web socket for a document\n * @param driveId - The SPO drive id that this request should be made against\n * @param itemId -The SPO item id that this request should be made against\n * @param siteUrl - The SPO site that this request should be made against\n * @param path - The API path that is relevant to this request\n * @param method - The type of request, such as GET or POST\n * @param logger - A logger to use for this request\n * @param getStorageToken - A function that is able to provide the access token for this request\n * @param epochTracker - fetch wrapper which incorporates epoch logic around joinSession call\n * @param requestSocketToken - flag indicating whether joinSession is expected to return access token\n * which is used when establishing websocket connection with collab session backend service.\n * @param guestDisplayName - display name used to identify guest user joining a session.\n * This is optional and used only when collab session is being joined via invite.\n */\nexport async function fetchJoinSession(\n urlParts: IOdspUrlParts,\n path: string,\n method: string,\n logger: ITelemetryLogger,\n getStorageToken: InstrumentedStorageTokenFetcher,\n epochTracker: EpochTracker,\n requestSocketToken: boolean,\n guestDisplayName?: string,\n): Promise<ISocketStorageDiscovery> {\n return getWithRetryForTokenRefresh(async (options) => {\n const token = await getStorageToken(options, \"JoinSession\");\n\n const extraProps = options.refresh\n ? { hasClaims: !!options.claims, hasTenantId: !!options.tenantId }\n : {};\n return PerformanceEvent.timedExecAsync(\n logger, {\n eventName: \"JoinSession\",\n attempts: options.refresh ? 2 : 1,\n ...extraProps,\n },\n async (event) => {\n // TODO Extract the auth header-vs-query logic out\n const siteOrigin = getOrigin(urlParts.siteUrl);\n let queryParams = `access_token=${token}`;\n let headers = {};\n if (queryParams.length > 2048) {\n queryParams = \"\";\n headers = { Authorization: `Bearer ${token}` };\n }\n let body: IJoinSessionBody | undefined;\n if (requestSocketToken || guestDisplayName) {\n body = {};\n if (requestSocketToken) {\n body.requestSocketToken = true;\n }\n if (guestDisplayName) {\n body.guestDisplayName = guestDisplayName;\n }\n // IMPORTANT: Must set content-type header explicitly to application/json when request has body.\n // By default, request will use text/plain as content-type and will be rejected by backend.\n headers[\"Content-Type\"] = \"application/json\";\n }\n\n const response = await runWithRetry(\n async () => epochTracker.fetchAndParseAsJSON<ISocketStorageDiscovery>(\n `${getApiRoot(siteOrigin)}/drives/${\n urlParts.driveId\n }/items/${urlParts.itemId}/${path}?${queryParams}`,\n { method, headers, body: body ? JSON.stringify(body) : undefined },\n \"joinSession\",\n ),\n \"joinSession\",\n logger,\n );\n\n // TODO SPO-specific telemetry\n event.end({\n ...response.commonSpoHeaders,\n // pushV2 websocket urls will contain pushf\n pushv2: response.content.deltaStreamSocketUrl.includes(\"pushf\"),\n });\n\n if (response.content.runtimeTenantId && !response.content.tenantId) {\n response.content.tenantId = response.content.runtimeTenantId;\n }\n\n return response.content;\n });\n });\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fluidframework/odsp-driver",
3
- "version": "0.47.0",
3
+ "version": "0.48.0",
4
4
  "description": "Socket storage implementation for SPO and ODC",
5
5
  "homepage": "https://fluidframework.com",
6
6
  "repository": "https://github.com/microsoft/FluidFramework",
@@ -58,15 +58,15 @@
58
58
  "@fluidframework/common-definitions": "^0.20.1",
59
59
  "@fluidframework/common-utils": "^0.32.1",
60
60
  "@fluidframework/core-interfaces": "^0.39.7",
61
- "@fluidframework/driver-base": "^0.47.0",
61
+ "@fluidframework/driver-base": "^0.48.0",
62
62
  "@fluidframework/driver-definitions": "^0.39.6",
63
- "@fluidframework/driver-utils": "^0.47.0",
64
- "@fluidframework/gitresources": "^0.1030.0",
65
- "@fluidframework/odsp-doclib-utils": "^0.47.0",
66
- "@fluidframework/odsp-driver-definitions": "^0.47.0",
67
- "@fluidframework/protocol-base": "^0.1030.0",
63
+ "@fluidframework/driver-utils": "^0.48.0",
64
+ "@fluidframework/gitresources": "^0.1031.0-37526",
65
+ "@fluidframework/odsp-doclib-utils": "^0.48.0",
66
+ "@fluidframework/odsp-driver-definitions": "^0.48.0",
67
+ "@fluidframework/protocol-base": "^0.1031.0-37526",
68
68
  "@fluidframework/protocol-definitions": "^0.1024.0",
69
- "@fluidframework/telemetry-utils": "^0.47.0",
69
+ "@fluidframework/telemetry-utils": "^0.48.0",
70
70
  "abort-controller": "^3.0.0",
71
71
  "node-fetch": "^2.6.1",
72
72
  "socket.io-client": "^2.4.0",
@@ -75,7 +75,7 @@
75
75
  "devDependencies": {
76
76
  "@fluidframework/build-common": "^0.23.0",
77
77
  "@fluidframework/eslint-config-fluid": "^0.23.0",
78
- "@fluidframework/mocha-test-setup": "^0.47.0",
78
+ "@fluidframework/mocha-test-setup": "^0.48.0",
79
79
  "@microsoft/api-extractor": "^7.16.1",
80
80
  "@types/mocha": "^8.2.2",
81
81
  "@types/node-fetch": "^2.5.10",
package/src/createFile.ts CHANGED
@@ -61,7 +61,7 @@ export async function createNewFluidFile(
61
61
  ): Promise<IOdspResolvedUrl> {
62
62
  // Check for valid filename before the request to create file is actually made.
63
63
  if (isInvalidFileName(newFileInfo.filename)) {
64
- throwOdspNetworkError("Invalid filename. Please try again.", invalidFileNameStatusCode);
64
+ throwOdspNetworkError("invalidFilename", invalidFileNameStatusCode);
65
65
  }
66
66
 
67
67
  let itemId: string;
@@ -132,7 +132,7 @@ export async function createNewEmptyFluidFile(
132
132
 
133
133
  const content = fetchResponse.content;
134
134
  if (!content || !content.id) {
135
- throwOdspNetworkError("Could not parse item from Vroom response", fetchIncorrectResponse);
135
+ throwOdspNetworkError("couldNotParseItemFromVroomResponse", fetchIncorrectResponse);
136
136
  }
137
137
  event.end({
138
138
  headers: Object.keys(headers).length !== 0 ? true : undefined,
@@ -186,7 +186,7 @@ export async function createNewFluidFileFromSummary(
186
186
 
187
187
  const content = fetchResponse.content;
188
188
  if (!content || !content.itemId) {
189
- throwOdspNetworkError("Could not parse item from Vroom response", fetchIncorrectResponse);
189
+ throwOdspNetworkError("couldNotParseItemFromVroomResponse", fetchIncorrectResponse);
190
190
  }
191
191
  event.end({
192
192
  headers: Object.keys(headers).length !== 0 ? true : undefined,
@@ -282,7 +282,7 @@ export class EpochTracker implements IPersistedFileCache {
282
282
  // initializes this value. Sometimes response does not contain epoch as it is still in
283
283
  // implementation phase at server side. In that case also, don't compare it with our epoch value.
284
284
  if (this.fluidEpoch && epochFromResponse && (this.fluidEpoch !== epochFromResponse)) {
285
- throwOdspNetworkError(message ?? "Epoch Mismatch", fluidEpochMismatchError);
285
+ throwOdspNetworkError(message ?? "epochMismatch", fluidEpochMismatchError);
286
286
  }
287
287
  }
288
288
 
@@ -121,6 +121,23 @@ export class OdspDeltaStorageWithCache implements IDocumentDeltaStorageService {
121
121
  ) {
122
122
  }
123
123
 
124
+ protected validateMessages(reason: string, messages: ISequencedDocumentMessage[], from: number) {
125
+ if (messages.length !== 0) {
126
+ const start = messages[0].sequenceNumber;
127
+ const length = messages.length;
128
+ const last = messages[length - 1].sequenceNumber;
129
+ if (start !== from) {
130
+ this.logger.sendErrorEvent({ eventName: "OpsFetchViolation", reason, from, start, last, length});
131
+ messages.length = 0;
132
+ }
133
+ if (last + 1 !== from + length) {
134
+ this.logger.sendErrorEvent({ eventName: "OpsFetchViolation", reason, from, start, last, length});
135
+ // we can do better here by finding consecutive sub-block and return it
136
+ messages.length = 0;
137
+ }
138
+ }
139
+ }
140
+
124
141
  public fetchMessages(
125
142
  fromTotal: number,
126
143
  toTotal: number | undefined,
@@ -137,44 +154,54 @@ export class OdspDeltaStorageWithCache implements IDocumentDeltaStorageService {
137
154
  let opsFromCache = 0;
138
155
  let opsFromStorage = 0;
139
156
 
140
- const stream = requestOps(
141
- async (from: number, to: number, telemetryProps: ITelemetryProperties) => {
142
- if (this.snapshotOps !== undefined && this.snapshotOps.length !== 0) {
143
- const messages = this.snapshotOps.filter((op) =>
144
- op.sequenceNumber >= from && op.sequenceNumber < to);
145
- if (messages.length > 0 && messages[0].sequenceNumber === from) {
146
- this.snapshotOps = this.snapshotOps.filter((op) => op.sequenceNumber >= to);
147
- opsFromSnapshot = messages.length;
148
- return { messages, partialResult: true };
149
- }
150
- this.snapshotOps = undefined;
157
+ const requestCallback = async (from: number, to: number, telemetryProps: ITelemetryProperties) => {
158
+ if (this.snapshotOps !== undefined && this.snapshotOps.length !== 0) {
159
+ const messages = this.snapshotOps.filter((op) =>
160
+ op.sequenceNumber >= from && op.sequenceNumber < to);
161
+ this.validateMessages("cached", messages, from);
162
+ if (messages.length > 0 && messages[0].sequenceNumber === from) {
163
+ this.snapshotOps = this.snapshotOps.filter((op) => op.sequenceNumber >= to);
164
+ opsFromSnapshot = messages.length;
165
+ return { messages, partialResult: true };
151
166
  }
167
+ this.snapshotOps = undefined;
168
+ }
152
169
 
153
- // Kick out request to PUSH for ops if it has them
154
- this.requestFromSocket(from, to);
155
-
156
- // Cache in normal flow is continuous. Once there is a miss, stop consulting cache.
157
- // This saves a bit of processing time
158
- if (from < this.firstCacheMiss) {
159
- const messagesFromCache = await this.getCached(from, to);
160
- if (messagesFromCache.length !== 0) {
161
- opsFromCache += messagesFromCache.length;
162
- return {
163
- messages: messagesFromCache,
164
- partialResult: true,
165
- };
166
- }
167
- this.firstCacheMiss = Math.min(this.firstCacheMiss, from);
170
+ // Kick out request to PUSH for ops if it has them
171
+ this.requestFromSocket(from, to);
172
+
173
+ // Cache in normal flow is continuous. Once there is a miss, stop consulting cache.
174
+ // This saves a bit of processing time
175
+ if (from < this.firstCacheMiss) {
176
+ const messagesFromCache = await this.getCached(from, to);
177
+ this.validateMessages("cached", messagesFromCache, from);
178
+ if (messagesFromCache.length !== 0) {
179
+ opsFromCache += messagesFromCache.length;
180
+ return {
181
+ messages: messagesFromCache,
182
+ partialResult: true,
183
+ };
168
184
  }
185
+ this.firstCacheMiss = Math.min(this.firstCacheMiss, from);
186
+ }
169
187
 
170
- if (cachedOnly) {
171
- return { messages: [], partialResult: false };
172
- }
188
+ if (cachedOnly) {
189
+ return { messages: [], partialResult: false };
190
+ }
173
191
 
174
- const ops = await this.getFromStorage(from, to, telemetryProps);
175
- opsFromStorage += ops.messages.length;
176
- this.opsReceived(ops.messages);
177
- return ops;
192
+ const ops = await this.getFromStorage(from, to, telemetryProps);
193
+ this.validateMessages("storage", ops.messages, from);
194
+ opsFromStorage += ops.messages.length;
195
+ this.opsReceived(ops.messages);
196
+ return ops;
197
+ };
198
+
199
+ const stream = requestOps(
200
+ async (from: number, to: number, telemetryProps: ITelemetryProperties) => {
201
+ const result = await requestCallback(from, to, telemetryProps);
202
+ // Catch all case, just in case
203
+ this.validateMessages("catch all", result.messages, from);
204
+ return result;
178
205
  },
179
206
  // Staging: starting with no concurrency, listening for feedback first.
180
207
  // In future releases we will switch to actual concurrency
@@ -432,18 +432,26 @@ export class OdspDocumentDeltaConnection extends DocumentDeltaConnection {
432
432
  // If so, there it most likely does not need these ops (otherwise it already asked for them)
433
433
  if (data !== undefined) {
434
434
  this.getOpsMap.delete(result.nonce);
435
+ const common = {
436
+ eventName: "GetOps",
437
+ code: result.code,
438
+ from: data.from,
439
+ to: data.to,
440
+ duration: performance.now() - data.start,
441
+ };
435
442
  if (messages !== undefined && messages.length > 0) {
436
443
  this.logger.sendPerformanceEvent({
437
- eventName: "GetOps",
444
+ ...common,
438
445
  first: messages[0].sequenceNumber,
439
446
  last: messages[messages.length - 1].sequenceNumber,
440
- code: result.code,
441
- from: data.from,
442
- to: data.to,
443
- duration: performance.now() - data.start,
444
447
  length: messages.length,
445
448
  });
446
449
  this.emit("op", this.documentId, messages);
450
+ } else {
451
+ this.logger.sendPerformanceEvent({
452
+ ...common,
453
+ length: 0,
454
+ });
447
455
  }
448
456
  }
449
457
  });
@@ -268,7 +268,7 @@ export class OdspDocumentService implements IDocumentService {
268
268
 
269
269
  const finalWebsocketToken = websocketToken ?? (websocketEndpoint.socketToken || null);
270
270
  if (finalWebsocketToken === null) {
271
- throwOdspNetworkError("Push Token is null", fetchTokenErrorCode);
271
+ throwOdspNetworkError("pushTokenIsNull", fetchTokenErrorCode);
272
272
  }
273
273
  try {
274
274
  const connection = await this.connectToDeltaStreamWithRetry(
@@ -391,10 +391,10 @@ export class OdspDocumentService implements IDocumentService {
391
391
  write: async (key: string, opsData: string) => {
392
392
  return this.cache.persistedCache.put({...opsKey, key}, opsData);
393
393
  },
394
- read: async (batch: string) => undefined,
394
+ read: async (key: string) => this.cache.persistedCache.get({...opsKey, key}),
395
395
  remove: () => { this.cache.persistedCache.removeEntries().catch(() => {}); },
396
396
  },
397
- this.hostPolicy.opsCaching?.batchSize ?? 100,
397
+ batchSize,
398
398
  this.hostPolicy.opsCaching?.timerGranularity ?? 5000,
399
399
  this.hostPolicy.opsCaching?.totalOpsToCache ?? 5000,
400
400
  );
@@ -485,10 +485,10 @@ export class OdspDocumentStorageService implements IDocumentStorageService {
485
485
  );
486
486
  const versionsResponse = response.content;
487
487
  if (!versionsResponse) {
488
- throwOdspNetworkError("getVersions returned no response", 400);
488
+ throwOdspNetworkError("getVersionsReturnedNoResponse", 400);
489
489
  }
490
490
  if (!Array.isArray(versionsResponse.value)) {
491
- throwOdspNetworkError("getVersions returned non-array response", 400);
491
+ throwOdspNetworkError("getVersionsReturnedNonArrayResponse", 400);
492
492
  }
493
493
  return versionsResponse.value.map((version) => {
494
494
  // Parse the date from the message
@@ -660,19 +660,19 @@ export class OdspDocumentStorageService implements IDocumentStorageService {
660
660
 
661
661
  private checkSnapshotUrl() {
662
662
  if (!this.snapshotUrl) {
663
- throwOdspNetworkError("Method not supported because no snapshotUrl was provided", 400);
663
+ throwOdspNetworkError("methodNotSupportedBecauseNoSnapshotUrlWasProvided", 400);
664
664
  }
665
665
  }
666
666
 
667
667
  private checkAttachmentPOSTUrl() {
668
668
  if (!this.attachmentPOSTUrl) {
669
- throwOdspNetworkError("Method not supported because no attachmentPOSTUrl was provided", 400);
669
+ throwOdspNetworkError("methodNotSupportedBecauseNoAttachmentPOSTUrlWasProvided", 400);
670
670
  }
671
671
  }
672
672
 
673
673
  private checkAttachmentGETUrl() {
674
674
  if (!this.attachmentGETUrl) {
675
- throwOdspNetworkError("Method not supported because no attachmentGETUrl was provided", 400);
675
+ throwOdspNetworkError("methodNotSupportedBecauseNoAttachmentGETUrlWasProvided", 400);
676
676
  }
677
677
  }
678
678
 
@@ -157,7 +157,7 @@ export class OdspDriverUrlResolverForShareLink implements IUrlResolver {
157
157
  { eventName: "GetSharingLinkToken" },
158
158
  async (event) => tokenFetcher(options).then((tokenResponse) => {
159
159
  if (tokenResponse === null) {
160
- throwOdspNetworkError("Share link Token is null", fetchTokenErrorCode);
160
+ throwOdspNetworkError("shareLinkTokenIsNull", fetchTokenErrorCode);
161
161
  }
162
162
  event.end({ fromCache: isTokenFromCache(tokenResponse) });
163
163
  return tokenResponse;
package/src/odspUtils.ts CHANGED
@@ -105,7 +105,7 @@ export async function fetchHelper(
105
105
  const response = fetchResponse as any as Response;
106
106
  // Let's assume we can retry.
107
107
  if (!response) {
108
- throwOdspNetworkError(`No response from the server`, fetchIncorrectResponse);
108
+ throwOdspNetworkError(`noResponseFromTheServer`, fetchIncorrectResponse);
109
109
  }
110
110
  if (!response.ok || response.status < 200 || response.status >= 300) {
111
111
  throwOdspNetworkError(
@@ -129,10 +129,10 @@ export async function fetchHelper(
129
129
  online = OnlineStatus.Offline;
130
130
  }
131
131
  if (error.name === "AbortError") {
132
- throwOdspNetworkError("Timeout during fetch", fetchTimeoutStatusCode);
132
+ throwOdspNetworkError("timeoutDuringFetch", fetchTimeoutStatusCode);
133
133
  }
134
134
  if (errorText.indexOf("ETIMEDOUT") !== -1) {
135
- throwOdspNetworkError("Timeout during fetch (ETIMEDOUT)", fetchTimeoutStatusCode);
135
+ throwOdspNetworkError("timeoutDuringFetch(ETIMEDOUT)", fetchTimeoutStatusCode);
136
136
  }
137
137
 
138
138
  //
@@ -141,7 +141,7 @@ export async function fetchHelper(
141
141
  // It is also non-serializable object due to circular references.
142
142
  //
143
143
  throwOdspNetworkError(
144
- `Fetch error`,
144
+ `fetchError`,
145
145
  online === OnlineStatus.Offline ? offlineFetchFailureStatusCode : fetchFailureStatusCode,
146
146
  undefined, // response
147
147
  );
@@ -195,7 +195,13 @@ export async function fetchAndParseAsJSONHelper<T>(
195
195
  };
196
196
  return res;
197
197
  } catch (e) {
198
- throwOdspNetworkError(`Error while parsing fetch response: ${e}`, fetchIncorrectResponse, content);
198
+ throwOdspNetworkError(
199
+ "errorWhileParsingFetchResponse",
200
+ fetchIncorrectResponse,
201
+ content,
202
+ undefined,
203
+ { error: Object(e) },
204
+ );
199
205
  }
200
206
  }
201
207
 
@@ -279,7 +285,7 @@ export function toInstrumentedOdspTokenFetcher(
279
285
  event.end({ fromCache: isTokenFromCache(tokenResponse), isNull: token === null });
280
286
  }
281
287
  if (token === null && throwOnNullToken) {
282
- throwOdspNetworkError(`${name} Token is null`, fetchTokenErrorCode);
288
+ throwOdspNetworkError("tokenIsNull", fetchTokenErrorCode, undefined, undefined, { method: name });
283
289
  }
284
290
  return token;
285
291
  }),
package/src/opsCaching.ts CHANGED
@@ -117,38 +117,43 @@ export class OpsCache {
117
117
  }
118
118
  }
119
119
 
120
- public async get(from: number, to?: number): Promise<IMessage[]> {
120
+ private async getCore(from: number, to?: number): Promise<IMessage[]> {
121
121
  const messages: IMessage[] = [];
122
- let batchNumber = this.getBatchNumber(from + 1);
123
- const start = performance.now();
122
+ let batchNumber = this.getBatchNumber(from);
124
123
  // eslint-disable-next-line no-constant-condition
125
124
  while (true) {
126
125
  const res = await this.cache.read(`${this.batchSize}_${batchNumber}`);
127
126
  if (res === undefined) {
128
- break;
127
+ return messages;
129
128
  }
130
129
  const result: CacheEntry = JSON.parse(res);
131
130
  for (const op of result) {
132
131
  // Note that we write out undefined, but due to JSON.stringify, it turns into null!
133
132
  if (op) {
134
133
  if (to !== undefined && op.sequenceNumber >= to) {
135
- break;
134
+ return messages;
136
135
  }
137
136
  if (messages.length === 0) {
138
- if (op.sequenceNumber > from + 1) {
139
- break;
140
- } else if (op.sequenceNumber <= from) {
137
+ if (op.sequenceNumber > from) {
138
+ return messages;
139
+ } else if (op.sequenceNumber < from) {
141
140
  continue;
142
141
  }
143
142
  }
144
143
  messages.push(op);
145
144
  } else if (messages.length !== 0) {
146
- break;
145
+ return messages;
147
146
  }
148
147
  }
149
148
 
150
149
  batchNumber++;
151
150
  }
151
+ }
152
+
153
+ public async get(from: number, to?: number): Promise<IMessage[]> {
154
+ const start = performance.now();
155
+
156
+ const messages = await this.getCore(from, to);
152
157
 
153
158
  const duration = performance.now() - start;
154
159
  if (messages.length > 0 || duration > 1000) {
@@ -6,4 +6,4 @@
6
6
  */
7
7
 
8
8
  export const pkgName = "@fluidframework/odsp-driver";
9
- export const pkgVersion = "0.47.0";
9
+ export const pkgVersion = "0.48.0";
package/src/vroom.ts CHANGED
@@ -72,6 +72,9 @@ export async function fetchJoinSession(
72
72
  if (guestDisplayName) {
73
73
  body.guestDisplayName = guestDisplayName;
74
74
  }
75
+ // IMPORTANT: Must set content-type header explicitly to application/json when request has body.
76
+ // By default, request will use text/plain as content-type and will be rejected by backend.
77
+ headers["Content-Type"] = "application/json";
75
78
  }
76
79
 
77
80
  const response = await runWithRetry(