@fluidframework/odsp-driver 0.59.2001 → 0.59.3000
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.eslintrc.js +0 -1
- package/dist/ReadBufferUtils.js +2 -2
- package/dist/ReadBufferUtils.js.map +1 -1
- package/dist/WriteBufferUtils.js +14 -14
- package/dist/WriteBufferUtils.js.map +1 -1
- package/dist/checkUrl.js +1 -1
- package/dist/checkUrl.js.map +1 -1
- package/dist/compactSnapshotParser.js +27 -27
- package/dist/compactSnapshotParser.js.map +1 -1
- package/dist/compactSnapshotWriter.js +15 -15
- package/dist/compactSnapshotWriter.js.map +1 -1
- package/dist/contracts.d.ts +0 -1
- package/dist/contracts.d.ts.map +1 -1
- package/dist/contracts.js.map +1 -1
- package/dist/createFile.js +15 -15
- package/dist/createFile.js.map +1 -1
- package/dist/createNewUtils.js +4 -4
- package/dist/createNewUtils.js.map +1 -1
- package/dist/epochTracker.d.ts.map +1 -1
- package/dist/epochTracker.js +11 -11
- package/dist/epochTracker.js.map +1 -1
- package/dist/fetch.js +1 -1
- package/dist/fetch.js.map +1 -1
- package/dist/fetchSnapshot.d.ts.map +1 -1
- package/dist/fetchSnapshot.js +19 -19
- package/dist/fetchSnapshot.js.map +1 -1
- package/dist/getFileLink.js +19 -19
- package/dist/getFileLink.js.map +1 -1
- package/dist/getQueryString.d.ts.map +1 -1
- package/dist/getQueryString.js.map +1 -1
- package/dist/getUrlAndHeadersWithAuth.d.ts.map +1 -1
- package/dist/getUrlAndHeadersWithAuth.js.map +1 -1
- package/dist/odspCache.d.ts.map +1 -1
- package/dist/odspCache.js.map +1 -1
- package/dist/odspDeltaStorageService.d.ts.map +1 -1
- package/dist/odspDeltaStorageService.js +5 -5
- package/dist/odspDeltaStorageService.js.map +1 -1
- package/dist/odspDocumentDeltaConnection.d.ts.map +1 -1
- package/dist/odspDocumentDeltaConnection.js +12 -12
- package/dist/odspDocumentDeltaConnection.js.map +1 -1
- package/dist/odspDocumentService.js +9 -8
- package/dist/odspDocumentService.js.map +1 -1
- package/dist/odspDocumentServiceFactory.js +1 -1
- package/dist/odspDocumentServiceFactory.js.map +1 -1
- package/dist/odspDocumentServiceFactoryCore.d.ts.map +1 -1
- package/dist/odspDocumentServiceFactoryCore.js +20 -12
- package/dist/odspDocumentServiceFactoryCore.js.map +1 -1
- package/dist/odspDocumentStorageManager.d.ts +1 -0
- package/dist/odspDocumentStorageManager.d.ts.map +1 -1
- package/dist/odspDocumentStorageManager.js +34 -32
- package/dist/odspDocumentStorageManager.js.map +1 -1
- package/dist/odspDriverUrlResolver.js +10 -10
- package/dist/odspDriverUrlResolver.js.map +1 -1
- package/dist/odspDriverUrlResolverForShareLink.js +10 -10
- package/dist/odspDriverUrlResolverForShareLink.js.map +1 -1
- package/dist/odspError.d.ts +1 -1
- package/dist/odspError.d.ts.map +1 -1
- package/dist/odspError.js +1 -1
- package/dist/odspError.js.map +1 -1
- package/dist/odspFluidFileLink.js +2 -2
- package/dist/odspFluidFileLink.js.map +1 -1
- package/dist/odspPublicUtils.js +1 -1
- package/dist/odspPublicUtils.js.map +1 -1
- package/dist/odspSnapshotParser.js +3 -3
- package/dist/odspSnapshotParser.js.map +1 -1
- package/dist/odspSummaryUploadManager.js +7 -7
- package/dist/odspSummaryUploadManager.js.map +1 -1
- package/dist/odspUtils.js +9 -9
- package/dist/odspUtils.js.map +1 -1
- package/dist/opsCaching.js.map +1 -1
- package/dist/packageVersion.d.ts +1 -1
- package/dist/packageVersion.js +1 -1
- package/dist/packageVersion.js.map +1 -1
- package/dist/prefetchLatestSnapshot.js +7 -7
- package/dist/prefetchLatestSnapshot.js.map +1 -1
- package/dist/retryErrorsStorageAdapter.d.ts.map +1 -1
- package/dist/retryErrorsStorageAdapter.js +1 -1
- package/dist/retryErrorsStorageAdapter.js.map +1 -1
- package/dist/retryUtils.js +4 -4
- package/dist/retryUtils.js.map +1 -1
- package/dist/vroom.js +3 -3
- package/dist/vroom.js.map +1 -1
- package/dist/zipItDataRepresentationUtils.d.ts.map +1 -1
- package/dist/zipItDataRepresentationUtils.js +21 -12
- package/dist/zipItDataRepresentationUtils.js.map +1 -1
- package/lib/WriteBufferUtils.js +1 -1
- package/lib/WriteBufferUtils.js.map +1 -1
- package/lib/compactSnapshotParser.js.map +1 -1
- package/lib/compactSnapshotWriter.js.map +1 -1
- package/lib/contracts.d.ts +0 -1
- package/lib/contracts.d.ts.map +1 -1
- package/lib/contracts.js.map +1 -1
- package/lib/createFile.js.map +1 -1
- package/lib/epochTracker.d.ts.map +1 -1
- package/lib/epochTracker.js +1 -1
- package/lib/epochTracker.js.map +1 -1
- package/lib/fetchSnapshot.d.ts.map +1 -1
- package/lib/fetchSnapshot.js.map +1 -1
- package/lib/getFileLink.js +6 -6
- package/lib/getFileLink.js.map +1 -1
- package/lib/getQueryString.d.ts.map +1 -1
- package/lib/getQueryString.js.map +1 -1
- package/lib/getUrlAndHeadersWithAuth.d.ts.map +1 -1
- package/lib/getUrlAndHeadersWithAuth.js.map +1 -1
- package/lib/odspCache.d.ts.map +1 -1
- package/lib/odspCache.js.map +1 -1
- package/lib/odspDeltaStorageService.d.ts.map +1 -1
- package/lib/odspDeltaStorageService.js.map +1 -1
- package/lib/odspDocumentDeltaConnection.d.ts.map +1 -1
- package/lib/odspDocumentDeltaConnection.js +1 -1
- package/lib/odspDocumentDeltaConnection.js.map +1 -1
- package/lib/odspDocumentService.js +2 -1
- package/lib/odspDocumentService.js.map +1 -1
- package/lib/odspDocumentServiceFactoryCore.d.ts.map +1 -1
- package/lib/odspDocumentServiceFactoryCore.js +10 -2
- package/lib/odspDocumentServiceFactoryCore.js.map +1 -1
- package/lib/odspDocumentStorageManager.d.ts +1 -0
- package/lib/odspDocumentStorageManager.d.ts.map +1 -1
- package/lib/odspDocumentStorageManager.js +17 -15
- package/lib/odspDocumentStorageManager.js.map +1 -1
- package/lib/odspDriverUrlResolver.js +5 -5
- package/lib/odspDriverUrlResolver.js.map +1 -1
- package/lib/odspDriverUrlResolverForShareLink.js.map +1 -1
- package/lib/odspError.d.ts +1 -1
- package/lib/odspError.d.ts.map +1 -1
- package/lib/odspFluidFileLink.js.map +1 -1
- package/lib/odspSnapshotParser.js +1 -1
- package/lib/odspSnapshotParser.js.map +1 -1
- package/lib/odspUtils.js.map +1 -1
- package/lib/opsCaching.js.map +1 -1
- package/lib/packageVersion.d.ts +1 -1
- package/lib/packageVersion.js +1 -1
- package/lib/packageVersion.js.map +1 -1
- package/lib/retryErrorsStorageAdapter.d.ts.map +1 -1
- package/lib/retryErrorsStorageAdapter.js.map +1 -1
- package/lib/retryUtils.js +1 -1
- package/lib/retryUtils.js.map +1 -1
- package/lib/vroom.js.map +1 -1
- package/lib/zipItDataRepresentationUtils.d.ts.map +1 -1
- package/lib/zipItDataRepresentationUtils.js +12 -3
- package/lib/zipItDataRepresentationUtils.js.map +1 -1
- package/package.json +14 -13
- package/src/compactSnapshotParser.ts +2 -2
- package/src/contracts.ts +4 -5
- package/src/createFile.ts +2 -2
- package/src/epochTracker.ts +9 -10
- package/src/fetchSnapshot.ts +10 -10
- package/src/getFileLink.ts +10 -10
- package/src/getQueryString.ts +1 -1
- package/src/getUrlAndHeadersWithAuth.ts +1 -1
- package/src/odspCache.ts +2 -2
- package/src/odspDeltaStorageService.ts +4 -5
- package/src/odspDocumentDeltaConnection.ts +2 -2
- package/src/odspDocumentService.ts +5 -5
- package/src/odspDocumentServiceFactoryCore.ts +13 -1
- package/src/odspDocumentStorageManager.ts +25 -18
- package/src/odspDriverUrlResolver.ts +2 -2
- package/src/odspDriverUrlResolverForShareLink.ts +1 -1
- package/src/odspSnapshotParser.ts +2 -2
- package/src/odspUtils.ts +6 -6
- package/src/opsCaching.ts +2 -2
- package/src/packageVersion.ts +1 -1
- package/src/retryErrorsStorageAdapter.ts +1 -1
- package/src/vroom.ts +1 -1
- package/src/zipItDataRepresentationUtils.ts +15 -13
package/src/createFile.ts
CHANGED
|
@@ -87,13 +87,13 @@ export async function createNewFluidFile(
|
|
|
87
87
|
sharingLinkErrorReason = content.sharingLinkErrorReason;
|
|
88
88
|
}
|
|
89
89
|
|
|
90
|
-
const odspUrl = createOdspUrl({... newFileInfo, itemId, dataStorePath: "/"});
|
|
90
|
+
const odspUrl = createOdspUrl({ ... newFileInfo, itemId, dataStorePath: "/" });
|
|
91
91
|
const resolver = new OdspDriverUrlResolver();
|
|
92
92
|
const odspResolvedUrl = await resolver.resolve({ url: odspUrl });
|
|
93
93
|
fileEntry.docId = odspResolvedUrl.hashedDocumentId;
|
|
94
94
|
fileEntry.resolvedUrl = odspResolvedUrl;
|
|
95
95
|
|
|
96
|
-
if(sharingLink || sharingLinkErrorReason) {
|
|
96
|
+
if (sharingLink || sharingLinkErrorReason) {
|
|
97
97
|
odspResolvedUrl.shareLinkInfo = {
|
|
98
98
|
createLink: {
|
|
99
99
|
type: newFileInfo.createLinkType,
|
package/src/epochTracker.ts
CHANGED
|
@@ -198,8 +198,8 @@ export class EpochTracker implements IPersistedFileCache {
|
|
|
198
198
|
|
|
199
199
|
private async fetchCore<T>(
|
|
200
200
|
url: string,
|
|
201
|
-
fetchOptions: {[index: string]: any},
|
|
202
|
-
fetcher: (url: string, fetchOptions: {[index: string]: any}) => Promise<IOdspResponse<T>>,
|
|
201
|
+
fetchOptions: { [index: string]: any; },
|
|
202
|
+
fetcher: (url: string, fetchOptions: { [index: string]: any; }) => Promise<IOdspResponse<T>>,
|
|
203
203
|
fetchType: FetchType,
|
|
204
204
|
addInBody: boolean = false,
|
|
205
205
|
fetchReason?: string,
|
|
@@ -224,7 +224,7 @@ export class EpochTracker implements IPersistedFileCache {
|
|
|
224
224
|
await this.checkForEpochError(error, epochFromResponse, fetchType);
|
|
225
225
|
throw error;
|
|
226
226
|
}).catch((error) => {
|
|
227
|
-
const fluidError = normalizeError(error, { props: { XRequestStatsHeader: clientCorrelationId }});
|
|
227
|
+
const fluidError = normalizeError(error, { props: { XRequestStatsHeader: clientCorrelationId } });
|
|
228
228
|
throw fluidError;
|
|
229
229
|
});
|
|
230
230
|
}
|
|
@@ -239,7 +239,7 @@ export class EpochTracker implements IPersistedFileCache {
|
|
|
239
239
|
*/
|
|
240
240
|
public async fetchArray(
|
|
241
241
|
url: string,
|
|
242
|
-
fetchOptions: {[index: string]: any},
|
|
242
|
+
fetchOptions: { [index: string]: any; },
|
|
243
243
|
fetchType: FetchType,
|
|
244
244
|
addInBody: boolean = false,
|
|
245
245
|
fetchReason?: string,
|
|
@@ -254,7 +254,7 @@ export class EpochTracker implements IPersistedFileCache {
|
|
|
254
254
|
) {
|
|
255
255
|
const isClpCompliantApp = getOdspResolvedUrl(this.fileEntry.resolvedUrl).isClpCompliantApp;
|
|
256
256
|
if (addInBody) {
|
|
257
|
-
const headers: {[key: string]: string} = {};
|
|
257
|
+
const headers: { [key: string]: string; } = {};
|
|
258
258
|
headers["X-RequestStats"] = clientCorrelationId;
|
|
259
259
|
if (this.fluidEpoch !== undefined) {
|
|
260
260
|
headers["x-fluid-epoch"] = this.fluidEpoch;
|
|
@@ -281,14 +281,14 @@ export class EpochTracker implements IPersistedFileCache {
|
|
|
281
281
|
}
|
|
282
282
|
}
|
|
283
283
|
|
|
284
|
-
private addParamInBody(fetchOptions: RequestInit, headers: {[key: string]: string}) {
|
|
284
|
+
private addParamInBody(fetchOptions: RequestInit, headers: { [key: string]: string; }) {
|
|
285
285
|
// We use multi part form request for post body where we want to use this.
|
|
286
286
|
// So extract the form boundary to mark the end of form.
|
|
287
287
|
const body = fetchOptions.body;
|
|
288
288
|
assert(typeof body === "string", 0x21d /* "body is not string" */);
|
|
289
289
|
const splitBody = body.split("\r\n");
|
|
290
290
|
const firstLine = splitBody.shift();
|
|
291
|
-
assert(firstLine
|
|
291
|
+
assert(firstLine?.startsWith("--") === true, 0x21e /* "improper boundary format" */);
|
|
292
292
|
const formParams = [firstLine];
|
|
293
293
|
Object.entries(headers).forEach(([key, value]) => {
|
|
294
294
|
formParams.push(`${key}: ${value}`);
|
|
@@ -412,7 +412,7 @@ export class EpochTrackerWithRedemption extends EpochTracker {
|
|
|
412
412
|
|
|
413
413
|
public async fetchAndParseAsJSON<T>(
|
|
414
414
|
url: string,
|
|
415
|
-
fetchOptions: {[index: string]: any},
|
|
415
|
+
fetchOptions: { [index: string]: any; },
|
|
416
416
|
fetchType: FetchType,
|
|
417
417
|
addInBody: boolean = false,
|
|
418
418
|
fetchReason?: string,
|
|
@@ -475,8 +475,7 @@ export function createOdspCacheAndTracker(
|
|
|
475
475
|
persistedCacheArg: IPersistedCache,
|
|
476
476
|
nonpersistentCache: INonPersistentCache,
|
|
477
477
|
fileEntry: IFileEntry,
|
|
478
|
-
logger: ITelemetryLogger): ICacheAndTracker
|
|
479
|
-
{
|
|
478
|
+
logger: ITelemetryLogger): ICacheAndTracker {
|
|
480
479
|
const epochTracker = new EpochTrackerWithRedemption(persistedCacheArg, fileEntry, logger);
|
|
481
480
|
return {
|
|
482
481
|
cache: {
|
package/src/fetchSnapshot.ts
CHANGED
|
@@ -59,7 +59,7 @@ export async function fetchSnapshot(
|
|
|
59
59
|
fetchFullSnapshot: boolean,
|
|
60
60
|
forceAccessTokenViaAuthorizationHeader: boolean,
|
|
61
61
|
logger: ITelemetryLogger,
|
|
62
|
-
snapshotDownloader: (url: string, fetchOptions: {[index: string]: any}) => Promise<IOdspResponse<unknown>>,
|
|
62
|
+
snapshotDownloader: (url: string, fetchOptions: { [index: string]: any; }) => Promise<IOdspResponse<unknown>>,
|
|
63
63
|
): Promise<ISnapshotContents> {
|
|
64
64
|
const path = `/trees/${versionId}`;
|
|
65
65
|
let queryParams: ISnapshotOptions = {};
|
|
@@ -104,7 +104,7 @@ export async function fetchSnapshotWithRedeem(
|
|
|
104
104
|
): Promise<ISnapshotContents> {
|
|
105
105
|
// back-compat: This block to be removed with #8784 when we only consume/consider odsp resolvers that are >= 0.51
|
|
106
106
|
const sharingLinkToRedeem = (odspResolvedUrl as any).sharingLinkToRedeem;
|
|
107
|
-
if(sharingLinkToRedeem) {
|
|
107
|
+
if (sharingLinkToRedeem) {
|
|
108
108
|
odspResolvedUrl.shareLinkInfo = { ...odspResolvedUrl.shareLinkInfo, sharingLinkToRedeem };
|
|
109
109
|
}
|
|
110
110
|
|
|
@@ -323,7 +323,7 @@ async function fetchLatestSnapshotCore(
|
|
|
323
323
|
encodedBlobsSize,
|
|
324
324
|
sequenceNumber,
|
|
325
325
|
ops: snapshot.ops?.length ?? 0,
|
|
326
|
-
nonSysOps:
|
|
326
|
+
nonSysOps: snapshot.ops?.filter((op) => !isSystemMessage(op)).length ?? 0,
|
|
327
327
|
headers: Object.keys(response.requestHeaders).length !== 0 ? true : undefined,
|
|
328
328
|
// Interval between the first fetch until the last byte of the last redirect.
|
|
329
329
|
redirectTime,
|
|
@@ -366,16 +366,16 @@ async function fetchLatestSnapshotCore(
|
|
|
366
366
|
}
|
|
367
367
|
|
|
368
368
|
interface ISnapshotRequestAndResponseOptions {
|
|
369
|
-
odspSnapshotResponse: IOdspResponse<ISnapshotContents
|
|
370
|
-
requestUrl: string
|
|
371
|
-
requestHeaders: {[index: string]: any}
|
|
369
|
+
odspSnapshotResponse: IOdspResponse<ISnapshotContents>;
|
|
370
|
+
requestUrl: string;
|
|
371
|
+
requestHeaders: { [index: string]: any; };
|
|
372
372
|
}
|
|
373
373
|
|
|
374
374
|
function getFormBodyAndHeaders(
|
|
375
375
|
odspResolvedUrl: IOdspResolvedUrl,
|
|
376
376
|
storageToken: string,
|
|
377
377
|
snapshotOptions: ISnapshotOptions | undefined,
|
|
378
|
-
headers?: {[index: string]: string},
|
|
378
|
+
headers?: { [index: string]: string; },
|
|
379
379
|
) {
|
|
380
380
|
const formBoundary = uuid();
|
|
381
381
|
const formParams: string[] = [];
|
|
@@ -402,7 +402,7 @@ function getFormBodyAndHeaders(
|
|
|
402
402
|
formParams.push(`_post: 1`);
|
|
403
403
|
formParams.push(`\r\n--${formBoundary}--`);
|
|
404
404
|
const postBody = formParams.join("\r\n");
|
|
405
|
-
const header: {[index: string]: any} = {
|
|
405
|
+
const header: { [index: string]: any; } = {
|
|
406
406
|
"Content-Type": `multipart/form-data;boundary=${formBoundary}`,
|
|
407
407
|
};
|
|
408
408
|
return { body: postBody, headers: header };
|
|
@@ -453,7 +453,7 @@ export async function downloadSnapshot(
|
|
|
453
453
|
): Promise<ISnapshotRequestAndResponseOptions> {
|
|
454
454
|
// back-compat: This block to be removed with #8784 when we only consume/consider odsp resolvers that are >= 0.51
|
|
455
455
|
const sharingLinkToRedeem = (odspResolvedUrl as any).sharingLinkToRedeem;
|
|
456
|
-
if(sharingLinkToRedeem) {
|
|
456
|
+
if (sharingLinkToRedeem) {
|
|
457
457
|
odspResolvedUrl.shareLinkInfo = { ...odspResolvedUrl.shareLinkInfo, sharingLinkToRedeem };
|
|
458
458
|
}
|
|
459
459
|
|
|
@@ -463,7 +463,7 @@ export async function downloadSnapshot(
|
|
|
463
463
|
// Adding below header will make VROOM API return 404 instead of 308 and browser can intercept it.
|
|
464
464
|
// This error thrown by server will contain the new redirect location. Look at the 404 error parsing
|
|
465
465
|
// for futher reference here: \packages\utils\odsp-doclib-utils\src\odspErrorUtils.ts
|
|
466
|
-
const header = {prefer: "manualredirect"};
|
|
466
|
+
const header = { prefer: "manualredirect" };
|
|
467
467
|
const { body, headers } = getFormBodyAndHeaders(
|
|
468
468
|
odspResolvedUrl, storageToken, snapshotOptions, header);
|
|
469
469
|
const fetchOptions = {
|
package/src/getFileLink.ts
CHANGED
|
@@ -94,7 +94,7 @@ async function getFileLinkCore(
|
|
|
94
94
|
// ODSP link requires extra call to return link that is resistant to file being renamed or moved to different folder
|
|
95
95
|
return PerformanceEvent.timedExecAsync(
|
|
96
96
|
logger,
|
|
97
|
-
{ eventName: "odspFileLink", requestName: "
|
|
97
|
+
{ eventName: "odspFileLink", requestName: "getSharingLink" },
|
|
98
98
|
async (event) => {
|
|
99
99
|
let attempts = 0;
|
|
100
100
|
let additionalProps;
|
|
@@ -106,13 +106,13 @@ async function getFileLinkCore(
|
|
|
106
106
|
getToken,
|
|
107
107
|
true /* throwOnNullToken */,
|
|
108
108
|
);
|
|
109
|
-
const storageToken = await storageTokenFetcher(options,"GetFileLinkCore");
|
|
109
|
+
const storageToken = await storageTokenFetcher(options, "GetFileLinkCore");
|
|
110
110
|
assert(storageToken !== null,
|
|
111
111
|
0x2bb /* "Instrumented token fetcher with throwOnNullToken = true should never return null" */);
|
|
112
112
|
|
|
113
113
|
const { url, headers } = getUrlAndHeadersWithAuth(
|
|
114
|
-
`${odspUrlParts.siteUrl}/_api/web/
|
|
115
|
-
encodeURIComponent(`'${fileItem.webDavUrl}'`)
|
|
114
|
+
`${odspUrlParts.siteUrl}/_api/web/GetFileByServerRelativeUrl(@a1)/Linkingurl?@a1=${
|
|
115
|
+
encodeURIComponent(`'${new URL(fileItem.webDavUrl).pathname}'`)
|
|
116
116
|
}`,
|
|
117
117
|
storageToken,
|
|
118
118
|
false,
|
|
@@ -129,15 +129,15 @@ async function getFileLinkCore(
|
|
|
129
129
|
additionalProps = response.propsToLog;
|
|
130
130
|
|
|
131
131
|
const sharingInfo = await response.content.json();
|
|
132
|
-
const
|
|
133
|
-
if (typeof
|
|
132
|
+
const linkingUrl = sharingInfo?.d?.LinkingUrl;
|
|
133
|
+
if (typeof linkingUrl !== "string") {
|
|
134
134
|
// This will retry once in getWithRetryForTokenRefresh
|
|
135
135
|
throw new NonRetryableError(
|
|
136
|
-
"Malformed
|
|
136
|
+
"Malformed GetSharingLink response",
|
|
137
137
|
DriverErrorType.incorrectServerResponse,
|
|
138
138
|
{ driverVersion });
|
|
139
139
|
}
|
|
140
|
-
return
|
|
140
|
+
return linkingUrl;
|
|
141
141
|
});
|
|
142
142
|
event.end({ ...additionalProps, attempts });
|
|
143
143
|
return fileLink;
|
|
@@ -174,14 +174,14 @@ async function getFileItemLite(
|
|
|
174
174
|
let additionalProps;
|
|
175
175
|
const fileItem = await getWithRetryForTokenRefresh(async (options) => {
|
|
176
176
|
attempts++;
|
|
177
|
-
const {siteUrl, driveId, itemId} = odspUrlParts;
|
|
177
|
+
const { siteUrl, driveId, itemId } = odspUrlParts;
|
|
178
178
|
const storageTokenFetcher = toInstrumentedOdspTokenFetcher(
|
|
179
179
|
logger,
|
|
180
180
|
odspUrlParts,
|
|
181
181
|
getToken,
|
|
182
182
|
true /* throwOnNullToken */,
|
|
183
183
|
);
|
|
184
|
-
const storageToken = await storageTokenFetcher(options,"GetFileItemLite");
|
|
184
|
+
const storageToken = await storageTokenFetcher(options, "GetFileItemLite");
|
|
185
185
|
assert(storageToken !== null,
|
|
186
186
|
0x2bc /* "Instrumented token fetcher with throwOnNullToken =true should never return null" */);
|
|
187
187
|
|
package/src/getQueryString.ts
CHANGED
|
@@ -8,7 +8,7 @@ import { ISnapshotOptions } from "@fluidframework/odsp-driver-definitions";
|
|
|
8
8
|
* Generates query string from the given query parameters.
|
|
9
9
|
* @param queryParams - Query parameters from which to create a query.
|
|
10
10
|
*/
|
|
11
|
-
export function getQueryString(queryParams: { [key: string]: string | number } | ISnapshotOptions): string {
|
|
11
|
+
export function getQueryString(queryParams: { [key: string]: string | number; } | ISnapshotOptions): string {
|
|
12
12
|
let queryString = "";
|
|
13
13
|
for (const key of Object.keys(queryParams)) {
|
|
14
14
|
if (queryParams[key] !== undefined) {
|
|
@@ -7,7 +7,7 @@ export function getUrlAndHeadersWithAuth(
|
|
|
7
7
|
url: string,
|
|
8
8
|
token: string | null,
|
|
9
9
|
forceAccessTokenViaAuthorizationHeader: boolean,
|
|
10
|
-
): { url: string
|
|
10
|
+
): { url: string; headers: { [index: string]: string; }; } {
|
|
11
11
|
if (!token || token.length === 0) {
|
|
12
12
|
return { url, headers: {} };
|
|
13
13
|
}
|
package/src/odspCache.ts
CHANGED
|
@@ -100,7 +100,7 @@ export interface INonPersistentCache {
|
|
|
100
100
|
* Cache of joined/joining session info
|
|
101
101
|
*/
|
|
102
102
|
readonly sessionJoinCache: PromiseCache<string,
|
|
103
|
-
{ entryTime: number
|
|
103
|
+
{ entryTime: number; joinSessionResponse: ISocketStorageDiscovery; }>;
|
|
104
104
|
|
|
105
105
|
/**
|
|
106
106
|
* Cache of resolved/resolving file URLs
|
|
@@ -120,7 +120,7 @@ export interface IOdspCache extends INonPersistentCache {
|
|
|
120
120
|
|
|
121
121
|
export class NonPersistentCache implements INonPersistentCache {
|
|
122
122
|
public readonly sessionJoinCache =
|
|
123
|
-
new PromiseCache<string, { entryTime: number
|
|
123
|
+
new PromiseCache<string, { entryTime: number; joinSessionResponse: ISocketStorageDiscovery; }>();
|
|
124
124
|
|
|
125
125
|
public readonly fileUrlCache = new PromiseCache<string, IOdspResolvedUrl>();
|
|
126
126
|
}
|
|
@@ -56,7 +56,7 @@ export class OdspDeltaStorageService {
|
|
|
56
56
|
|
|
57
57
|
postBody += `_post: 1\r\n`;
|
|
58
58
|
postBody += `\r\n--${formBoundary}--`;
|
|
59
|
-
const headers: {[index: string]: any} = {
|
|
59
|
+
const headers: { [index: string]: any; } = {
|
|
60
60
|
"Content-Type": `multipart/form-data;boundary=${formBoundary}`,
|
|
61
61
|
};
|
|
62
62
|
|
|
@@ -139,11 +139,11 @@ export class OdspDeltaStorageWithCache implements IDocumentDeltaStorageService {
|
|
|
139
139
|
const length = messages.length;
|
|
140
140
|
const last = messages[length - 1].sequenceNumber;
|
|
141
141
|
if (start !== from) {
|
|
142
|
-
this.logger.sendErrorEvent({ eventName: "OpsFetchViolation", reason, from, start, last, length});
|
|
142
|
+
this.logger.sendErrorEvent({ eventName: "OpsFetchViolation", reason, from, start, last, length });
|
|
143
143
|
messages.length = 0;
|
|
144
144
|
}
|
|
145
145
|
if (last + 1 !== from + length) {
|
|
146
|
-
this.logger.sendErrorEvent({ eventName: "OpsFetchViolation", reason, from, start, last, length});
|
|
146
|
+
this.logger.sendErrorEvent({ eventName: "OpsFetchViolation", reason, from, start, last, length });
|
|
147
147
|
// we can do better here by finding consecutive sub-block and return it
|
|
148
148
|
messages.length = 0;
|
|
149
149
|
}
|
|
@@ -155,8 +155,7 @@ export class OdspDeltaStorageWithCache implements IDocumentDeltaStorageService {
|
|
|
155
155
|
toTotal: number | undefined,
|
|
156
156
|
abortSignal?: AbortSignal,
|
|
157
157
|
cachedOnly?: boolean,
|
|
158
|
-
fetchReason?: string)
|
|
159
|
-
{
|
|
158
|
+
fetchReason?: string) {
|
|
160
159
|
// We do not control what's in the cache. Current API assumes that fetchMessages() keeps banging on
|
|
161
160
|
// storage / cache until it gets ops it needs. This would result in deadlock if fixed range is asked from
|
|
162
161
|
// cache and it's not there.
|
|
@@ -58,7 +58,7 @@ class SocketReference extends TypedEventEmitter<ISocketEvents> {
|
|
|
58
58
|
const socketReference = SocketReference.socketIoSockets.get(key);
|
|
59
59
|
|
|
60
60
|
// Verify the socket is healthy before reusing it
|
|
61
|
-
if (socketReference
|
|
61
|
+
if (socketReference?.disconnected) {
|
|
62
62
|
// The socket is in a bad state. fully remove the reference
|
|
63
63
|
socketReference.closeSocket();
|
|
64
64
|
return undefined;
|
|
@@ -279,7 +279,7 @@ export class OdspDocumentDeltaConnection extends DocumentDeltaConnection {
|
|
|
279
279
|
|
|
280
280
|
private readonly requestOpsNoncePrefix: string;
|
|
281
281
|
private pushCallCounter = 0;
|
|
282
|
-
private readonly getOpsMap: Map<string, { start: number
|
|
282
|
+
private readonly getOpsMap: Map<string, { start: number; from: number; to: number; }> = new Map();
|
|
283
283
|
private flushOpNonce: string | undefined;
|
|
284
284
|
private flushDeferred: Deferred<FlushResult> | undefined;
|
|
285
285
|
|
|
@@ -237,7 +237,7 @@ export class OdspDocumentService implements IDocumentService {
|
|
|
237
237
|
return normalizeError(error, { props: {
|
|
238
238
|
failedConnectionStep,
|
|
239
239
|
separateTokenRequest,
|
|
240
|
-
}});
|
|
240
|
+
} });
|
|
241
241
|
}
|
|
242
242
|
|
|
243
243
|
/**
|
|
@@ -309,7 +309,7 @@ export class OdspDocumentService implements IDocumentService {
|
|
|
309
309
|
"createDeltaConnection",
|
|
310
310
|
!requestWebsocketTokenFromJoinSession);
|
|
311
311
|
if (typeof error === "object" && error !== null) {
|
|
312
|
-
normalizedError.addTelemetryProperties({socketDocumentId: websocketEndpoint.id});
|
|
312
|
+
normalizedError.addTelemetryProperties({ socketDocumentId: websocketEndpoint.id });
|
|
313
313
|
}
|
|
314
314
|
throw normalizedError;
|
|
315
315
|
}
|
|
@@ -351,7 +351,7 @@ export class OdspDocumentService implements IDocumentService {
|
|
|
351
351
|
// This document can only be opened in storage-only mode.
|
|
352
352
|
// DeltaManager will recognize this error
|
|
353
353
|
// and load without a delta stream connection.
|
|
354
|
-
this._policies = {...this._policies,storageOnly: true};
|
|
354
|
+
this._policies = { ...this._policies, storageOnly: true };
|
|
355
355
|
throw new DeltaStreamConnectionForbiddenError(code, { driverVersion });
|
|
356
356
|
default:
|
|
357
357
|
continue;
|
|
@@ -515,9 +515,9 @@ export class OdspDocumentService implements IDocumentService {
|
|
|
515
515
|
// ICache
|
|
516
516
|
{
|
|
517
517
|
write: async (key: string, opsData: string) => {
|
|
518
|
-
return this.cache.persistedCache.put({...opsKey, key}, opsData);
|
|
518
|
+
return this.cache.persistedCache.put({ ...opsKey, key }, opsData);
|
|
519
519
|
},
|
|
520
|
-
read: async (key: string) => this.cache.persistedCache.get({...opsKey, key}),
|
|
520
|
+
read: async (key: string) => this.cache.persistedCache.get({ ...opsKey, key }),
|
|
521
521
|
remove: () => { this.cache.persistedCache.removeEntries().catch(() => {}); },
|
|
522
522
|
},
|
|
523
523
|
batchSize,
|
|
@@ -14,7 +14,10 @@ import {
|
|
|
14
14
|
TelemetryLogger,
|
|
15
15
|
PerformanceEvent,
|
|
16
16
|
} from "@fluidframework/telemetry-utils";
|
|
17
|
-
import {
|
|
17
|
+
import {
|
|
18
|
+
getDocAttributesFromProtocolSummary,
|
|
19
|
+
ensureFluidResolvedUrl,
|
|
20
|
+
} from "@fluidframework/driver-utils";
|
|
18
21
|
import {
|
|
19
22
|
TokenFetchOptions,
|
|
20
23
|
OdspResourceTokenFetchOptions,
|
|
@@ -72,6 +75,15 @@ export class OdspDocumentServiceFactoryCore implements IDocumentServiceFactory {
|
|
|
72
75
|
if (filePath === undefined || filePath === null) {
|
|
73
76
|
throw new Error("File path should be provided!!");
|
|
74
77
|
}
|
|
78
|
+
|
|
79
|
+
const protocolSummary = createNewSummary?.tree[".protocol"];
|
|
80
|
+
if (protocolSummary) {
|
|
81
|
+
const documentAttributes = getDocAttributesFromProtocolSummary(protocolSummary as ISummaryTree);
|
|
82
|
+
if (documentAttributes?.sequenceNumber !== 0) {
|
|
83
|
+
throw new Error("Seq number in detached ODSP container should be 0");
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
75
87
|
const newFileInfo: INewFileInfo = {
|
|
76
88
|
driveId: odspResolvedUrl.driveId,
|
|
77
89
|
siteUrl: odspResolvedUrl.siteUrl,
|
|
@@ -45,10 +45,12 @@ import { OdspSummaryUploadManager } from "./odspSummaryUploadManager";
|
|
|
45
45
|
import { FlushResult } from "./odspDocumentDeltaConnection";
|
|
46
46
|
import { pkgVersion as driverVersion } from "./packageVersion";
|
|
47
47
|
|
|
48
|
+
export const defaultSummarizerCacheExpiryTimeout: number = 60 * 1000; // 60 seconds.
|
|
49
|
+
|
|
48
50
|
/* eslint-disable max-len */
|
|
49
51
|
|
|
50
52
|
// An implementation of Promise.race that gives you the winner of the promise race
|
|
51
|
-
async function promiseRaceWithWinner<T>(promises: Promise<T>[]): Promise<{ index: number
|
|
53
|
+
async function promiseRaceWithWinner<T>(promises: Promise<T>[]): Promise<{ index: number; value: T; }> {
|
|
52
54
|
return new Promise((resolve, reject) => {
|
|
53
55
|
promises.forEach((p, index) => {
|
|
54
56
|
p.then((v) => resolve({ index, value: v })).catch(reject);
|
|
@@ -152,6 +154,10 @@ class BlobCache {
|
|
|
152
154
|
}
|
|
153
155
|
}
|
|
154
156
|
|
|
157
|
+
interface GetVersionsTelemetryProps {
|
|
158
|
+
cacheEntryAge?: number;
|
|
159
|
+
cacheSummarizerExpired?: boolean;
|
|
160
|
+
}
|
|
155
161
|
export class OdspDocumentStorageService implements IDocumentStorageService {
|
|
156
162
|
readonly policies = {
|
|
157
163
|
// By default, ODSP tells the container not to prefetch/cache.
|
|
@@ -401,7 +407,7 @@ export class OdspDocumentStorageService implements IDocumentStorageService {
|
|
|
401
407
|
this.logger,
|
|
402
408
|
{ eventName: "ObtainSnapshot" },
|
|
403
409
|
async (event: PerformanceEvent) => {
|
|
404
|
-
const props = {};
|
|
410
|
+
const props: GetVersionsTelemetryProps = {};
|
|
405
411
|
let retrievedSnapshot: ISnapshotContents | undefined;
|
|
406
412
|
// Here's the logic to grab the persistent cache snapshot implemented by the host
|
|
407
413
|
// Epoch tracker is responsible for communicating with the persistent cache, handling epochs and cache versions
|
|
@@ -413,9 +419,21 @@ export class OdspDocumentStorageService implements IDocumentStorageService {
|
|
|
413
419
|
const age = Date.now() - (snapshotCachedEntry.cacheEntryTime ??
|
|
414
420
|
(Date.now() - 30 * 24 * 60 * 60 * 1000));
|
|
415
421
|
|
|
422
|
+
// In order to decrease the number of times we have to execute a snapshot refresh,
|
|
423
|
+
// if this is the summarizer and we have a cache entry but it is past the defaultSummarizerCacheExpiryTimeout,
|
|
424
|
+
// force the network retrieval instead as there might be a more recent snapshot available.
|
|
425
|
+
// See: https://github.com/microsoft/FluidFramework/issues/8995 for additional information.
|
|
426
|
+
if (this.hostPolicy.summarizerClient) {
|
|
427
|
+
if (age > defaultSummarizerCacheExpiryTimeout) {
|
|
428
|
+
props.cacheSummarizerExpired = true;
|
|
429
|
+
return undefined;
|
|
430
|
+
} else {
|
|
431
|
+
props.cacheSummarizerExpired = false;
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
|
|
416
435
|
// Record the cache age
|
|
417
|
-
|
|
418
|
-
props["cacheEntryAge"] = age;
|
|
436
|
+
props.cacheEntryAge = age;
|
|
419
437
|
}
|
|
420
438
|
|
|
421
439
|
return snapshotCachedEntry;
|
|
@@ -464,8 +482,7 @@ export class OdspDocumentStorageService implements IDocumentStorageService {
|
|
|
464
482
|
}
|
|
465
483
|
}
|
|
466
484
|
if (method === "network") {
|
|
467
|
-
|
|
468
|
-
props["cacheEntryAge"] = undefined;
|
|
485
|
+
props.cacheEntryAge = undefined;
|
|
469
486
|
}
|
|
470
487
|
event.end({ ...props, method });
|
|
471
488
|
return retrievedSnapshot;
|
|
@@ -495,7 +512,7 @@ export class OdspDocumentStorageService implements IDocumentStorageService {
|
|
|
495
512
|
return getWithRetryForTokenRefresh(async (options) => {
|
|
496
513
|
const storageToken = await this.getStorageToken(options, "GetVersions");
|
|
497
514
|
const { url, headers } = getUrlAndHeadersWithAuth(
|
|
498
|
-
`${this.snapshotUrl}/versions?
|
|
515
|
+
`${this.snapshotUrl}/versions?top=${count}`,
|
|
499
516
|
storageToken,
|
|
500
517
|
!!this.hostPolicy.sessionOptions?.forceAccessTokenViaAuthorizationHeader,
|
|
501
518
|
);
|
|
@@ -523,17 +540,7 @@ export class OdspDocumentStorageService implements IDocumentStorageService {
|
|
|
523
540
|
{ driverVersion });
|
|
524
541
|
}
|
|
525
542
|
return versionsResponse.value.map((version) => {
|
|
526
|
-
// Parse the date from the message
|
|
527
|
-
let date: string | undefined;
|
|
528
|
-
for (const rec of version.message.split("\n")) {
|
|
529
|
-
const index = rec.indexOf(":");
|
|
530
|
-
if (index !== -1 && rec.substr(0, index) === "Date") {
|
|
531
|
-
date = rec.substr(index + 1).trim();
|
|
532
|
-
break;
|
|
533
|
-
}
|
|
534
|
-
}
|
|
535
543
|
return {
|
|
536
|
-
date,
|
|
537
544
|
id: version.id,
|
|
538
545
|
treeId: undefined!,
|
|
539
546
|
};
|
|
@@ -727,7 +734,7 @@ export class OdspDocumentStorageService implements IDocumentStorageService {
|
|
|
727
734
|
if (!tree) {
|
|
728
735
|
tree = await getWithRetryForTokenRefresh(async (options) => {
|
|
729
736
|
const storageToken = await this.getStorageToken(options, "ReadCommit");
|
|
730
|
-
const snapshotDownloader = async (url: string, fetchOptions: {[index: string]: any}) => {
|
|
737
|
+
const snapshotDownloader = async (url: string, fetchOptions: { [index: string]: any; }) => {
|
|
731
738
|
return this.epochTracker.fetchAndParseAsJSON(
|
|
732
739
|
url,
|
|
733
740
|
fetchOptions,
|
|
@@ -66,7 +66,7 @@ export class OdspDriverUrlResolver implements IUrlResolver {
|
|
|
66
66
|
constructor() { }
|
|
67
67
|
|
|
68
68
|
public async resolve(request: IRequest): Promise<IOdspResolvedUrl> {
|
|
69
|
-
if (request.headers
|
|
69
|
+
if (request.headers?.[DriverHeader.createNew]) {
|
|
70
70
|
const [siteURL, queryString] = request.url.split("?");
|
|
71
71
|
|
|
72
72
|
const searchParams = new URLSearchParams(queryString);
|
|
@@ -82,7 +82,7 @@ export class OdspDriverUrlResolver implements IUrlResolver {
|
|
|
82
82
|
{ driverVersion: pkgVersion });
|
|
83
83
|
}
|
|
84
84
|
let shareLinkInfo: ShareLinkInfoType | undefined;
|
|
85
|
-
if(createLinkType && createLinkType in ShareLinkTypes) {
|
|
85
|
+
if (createLinkType && createLinkType in ShareLinkTypes) {
|
|
86
86
|
shareLinkInfo = {
|
|
87
87
|
createLink: {
|
|
88
88
|
type: ShareLinkTypes[createLinkType],
|
|
@@ -133,7 +133,7 @@ export class OdspDriverUrlResolverForShareLink implements IUrlResolver {
|
|
|
133
133
|
// when redeeming the share link during the redeem fallback for trees latest call becomes greater than
|
|
134
134
|
// the eligible length.
|
|
135
135
|
odspResolvedUrl.shareLinkInfo = Object.assign(odspResolvedUrl.shareLinkInfo || {},
|
|
136
|
-
{sharingLinkToRedeem: this.removeNavParam(request.url)});
|
|
136
|
+
{ sharingLinkToRedeem: this.removeNavParam(request.url) });
|
|
137
137
|
}
|
|
138
138
|
if (odspResolvedUrl.itemId) {
|
|
139
139
|
// Kick start the sharing link request if we don't have it already as a performance optimization.
|
|
@@ -16,7 +16,7 @@ import { ISnapshotContents } from "./odspUtils";
|
|
|
16
16
|
* @returns the hierarchical tree
|
|
17
17
|
*/
|
|
18
18
|
function buildHierarchy(flatTree: IOdspSnapshotCommit): api.ISnapshotTree {
|
|
19
|
-
const lookup: { [path: string]: api.ISnapshotTree } = {};
|
|
19
|
+
const lookup: { [path: string]: api.ISnapshotTree; } = {};
|
|
20
20
|
// id is required for root tree as it will be used to determine the version we loaded from.
|
|
21
21
|
const root: api.ISnapshotTree = { id: flatTree.id, blobs: {}, trees: {} };
|
|
22
22
|
lookup[""] = root;
|
|
@@ -64,7 +64,7 @@ export function convertOdspSnapshotToSnapsohtTreeAndBlobs(
|
|
|
64
64
|
const val: ISnapshotContents = {
|
|
65
65
|
blobs: blobsWithBufferContent,
|
|
66
66
|
ops: odspSnapshot.ops?.map((op) => op.op) ?? [],
|
|
67
|
-
sequenceNumber: odspSnapshot
|
|
67
|
+
sequenceNumber: odspSnapshot?.trees[0].sequenceNumber,
|
|
68
68
|
snapshotTree: buildHierarchy(odspSnapshot.trees[0]),
|
|
69
69
|
};
|
|
70
70
|
return val;
|
package/src/odspUtils.ts
CHANGED
|
@@ -44,17 +44,17 @@ export const getWithRetryForTokenRefreshRepeat = "getWithRetryForTokenRefreshRep
|
|
|
44
44
|
export const getOrigin = (url: string) => new URL(url).origin;
|
|
45
45
|
|
|
46
46
|
export interface ISnapshotContents {
|
|
47
|
-
snapshotTree: ISnapshotTree
|
|
48
|
-
blobs: Map<string, ArrayBuffer
|
|
49
|
-
ops: ISequencedDocumentMessage[]
|
|
50
|
-
sequenceNumber: number | undefined
|
|
47
|
+
snapshotTree: ISnapshotTree;
|
|
48
|
+
blobs: Map<string, ArrayBuffer>;
|
|
49
|
+
ops: ISequencedDocumentMessage[];
|
|
50
|
+
sequenceNumber: number | undefined;
|
|
51
51
|
}
|
|
52
52
|
|
|
53
53
|
export interface IOdspResponse<T> {
|
|
54
54
|
content: T;
|
|
55
55
|
headers: Map<string, string>;
|
|
56
56
|
propsToLog: ITelemetryProperties;
|
|
57
|
-
duration: number
|
|
57
|
+
duration: number;
|
|
58
58
|
}
|
|
59
59
|
|
|
60
60
|
export interface TokenFetchOptionsEx extends TokenFetchOptions {
|
|
@@ -246,7 +246,7 @@ export const createOdspLogger = (logger?: ITelemetryBaseLogger) =>
|
|
|
246
246
|
ChildLogger.create(
|
|
247
247
|
logger,
|
|
248
248
|
"OdspDriver",
|
|
249
|
-
{ all
|
|
249
|
+
{ all:
|
|
250
250
|
{
|
|
251
251
|
driverVersion,
|
|
252
252
|
},
|
package/src/opsCaching.ts
CHANGED
|
@@ -47,7 +47,7 @@ export class OpsCache {
|
|
|
47
47
|
if (remainingSlots !== 0) {
|
|
48
48
|
this.batches.set(this.getBatchNumber(startingSequenceNumber), {
|
|
49
49
|
remainingSlots,
|
|
50
|
-
batchData
|
|
50
|
+
batchData: this.initializeNewBatchDataArray(),
|
|
51
51
|
dirty: false,
|
|
52
52
|
});
|
|
53
53
|
}
|
|
@@ -109,7 +109,7 @@ export class OpsCache {
|
|
|
109
109
|
|
|
110
110
|
this.totalOpsToCache--;
|
|
111
111
|
if (this.totalOpsToCache === 0) {
|
|
112
|
-
this.logger.sendPerformanceEvent({ eventName: "CacheOpsLimitHit"});
|
|
112
|
+
this.logger.sendPerformanceEvent({ eventName: "CacheOpsLimitHit" });
|
|
113
113
|
this.cache.remove();
|
|
114
114
|
this.dispose();
|
|
115
115
|
break;
|
package/src/packageVersion.ts
CHANGED
|
@@ -31,7 +31,7 @@ export class RetryErrorsStorageAdapter implements IDocumentStorageService, IDisp
|
|
|
31
31
|
public get policies(): IDocumentStorageServicePolicies | undefined {
|
|
32
32
|
return this.internalStorageService.policies;
|
|
33
33
|
}
|
|
34
|
-
public get disposed() {return this._disposed;}
|
|
34
|
+
public get disposed() { return this._disposed; }
|
|
35
35
|
public dispose() {
|
|
36
36
|
this._disposed = true;
|
|
37
37
|
}
|
package/src/vroom.ts
CHANGED
|
@@ -83,7 +83,7 @@ export async function fetchJoinSession(
|
|
|
83
83
|
postBody += `\r\n${JSON.stringify(body)}\r\n`;
|
|
84
84
|
}
|
|
85
85
|
postBody += `\r\n--${formBoundary}--`;
|
|
86
|
-
const headers: {[index: string]: string} = {
|
|
86
|
+
const headers: { [index: string]: string; } = {
|
|
87
87
|
"Content-Type": `multipart/form-data;boundary=${formBoundary}`,
|
|
88
88
|
};
|
|
89
89
|
|