@fluidframework/container-loader 2.0.0-internal.7.0.0 → 2.0.0-internal.7.2.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.
- package/CHANGELOG.md +14 -0
- package/api-extractor.json +9 -1
- package/api-report/container-loader.api.md +153 -0
- package/dist/connectionManager.d.ts +2 -1
- package/dist/connectionManager.d.ts.map +1 -1
- package/dist/connectionManager.js +29 -20
- package/dist/connectionManager.js.map +1 -1
- package/dist/container.d.ts +6 -5
- package/dist/container.d.ts.map +1 -1
- package/dist/container.js +21 -19
- package/dist/container.js.map +1 -1
- package/dist/containerContext.d.ts +2 -2
- package/dist/containerContext.d.ts.map +1 -1
- package/dist/containerContext.js.map +1 -1
- package/dist/containerStorageAdapter.d.ts +1 -1
- package/dist/containerStorageAdapter.d.ts.map +1 -1
- package/dist/containerStorageAdapter.js +3 -3
- package/dist/containerStorageAdapter.js.map +1 -1
- package/dist/contracts.d.ts +4 -4
- package/dist/contracts.d.ts.map +1 -1
- package/dist/contracts.js.map +1 -1
- package/dist/debugLogger.d.ts.map +1 -1
- package/dist/debugLogger.js.map +1 -1
- package/dist/deltaManager.d.ts +1 -1
- package/dist/deltaManager.d.ts.map +1 -1
- package/dist/deltaManager.js +8 -9
- package/dist/deltaManager.js.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -1
- package/dist/index.js.map +1 -1
- package/dist/loader.d.ts +4 -4
- package/dist/loader.js +7 -7
- package/dist/loader.js.map +1 -1
- package/dist/location-redirection-utilities/index.d.ts +6 -0
- package/dist/location-redirection-utilities/index.d.ts.map +1 -0
- package/dist/location-redirection-utilities/index.js +11 -0
- package/dist/location-redirection-utilities/index.js.map +1 -0
- package/dist/location-redirection-utilities/resolveWithLocationRedirection.d.ts +22 -0
- package/dist/location-redirection-utilities/resolveWithLocationRedirection.d.ts.map +1 -0
- package/dist/location-redirection-utilities/resolveWithLocationRedirection.js +51 -0
- package/dist/location-redirection-utilities/resolveWithLocationRedirection.js.map +1 -0
- package/dist/packageVersion.d.ts +1 -1
- package/dist/packageVersion.js +1 -1
- package/dist/packageVersion.js.map +1 -1
- package/dist/retriableDocumentStorageService.d.ts +3 -2
- package/dist/retriableDocumentStorageService.d.ts.map +1 -1
- package/dist/retriableDocumentStorageService.js +18 -11
- package/dist/retriableDocumentStorageService.js.map +1 -1
- package/dist/tsdoc-metadata.json +1 -1
- package/dist/utils.d.ts +23 -1
- package/dist/utils.d.ts.map +1 -1
- package/dist/utils.js +11 -3
- package/dist/utils.js.map +1 -1
- package/lib/connectionManager.d.ts +2 -1
- package/lib/connectionManager.d.ts.map +1 -1
- package/lib/connectionManager.js +29 -20
- package/lib/connectionManager.js.map +1 -1
- package/lib/container.d.ts +6 -5
- package/lib/container.d.ts.map +1 -1
- package/lib/container.js +21 -19
- package/lib/container.js.map +1 -1
- package/lib/containerContext.d.ts +2 -2
- package/lib/containerContext.d.ts.map +1 -1
- package/lib/containerContext.js.map +1 -1
- package/lib/containerStorageAdapter.d.ts +1 -1
- package/lib/containerStorageAdapter.d.ts.map +1 -1
- package/lib/containerStorageAdapter.js +3 -3
- package/lib/containerStorageAdapter.js.map +1 -1
- package/lib/contracts.d.ts +4 -4
- package/lib/contracts.d.ts.map +1 -1
- package/lib/contracts.js.map +1 -1
- package/lib/debugLogger.d.ts.map +1 -1
- package/lib/debugLogger.js.map +1 -1
- package/lib/deltaManager.d.ts +1 -1
- package/lib/deltaManager.d.ts.map +1 -1
- package/lib/deltaManager.js +8 -9
- package/lib/deltaManager.js.map +1 -1
- package/lib/index.d.ts +2 -0
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +2 -0
- package/lib/index.js.map +1 -1
- package/lib/loader.d.ts +4 -4
- package/lib/loader.js +8 -8
- package/lib/loader.js.map +1 -1
- package/lib/location-redirection-utilities/index.d.ts +6 -0
- package/lib/location-redirection-utilities/index.d.ts.map +1 -0
- package/lib/location-redirection-utilities/index.js +6 -0
- package/lib/location-redirection-utilities/index.js.map +1 -0
- package/lib/location-redirection-utilities/resolveWithLocationRedirection.d.ts +22 -0
- package/lib/location-redirection-utilities/resolveWithLocationRedirection.d.ts.map +1 -0
- package/lib/location-redirection-utilities/resolveWithLocationRedirection.js +46 -0
- package/lib/location-redirection-utilities/resolveWithLocationRedirection.js.map +1 -0
- package/lib/packageVersion.d.ts +1 -1
- package/lib/packageVersion.js +1 -1
- package/lib/packageVersion.js.map +1 -1
- package/lib/retriableDocumentStorageService.d.ts +3 -2
- package/lib/retriableDocumentStorageService.d.ts.map +1 -1
- package/lib/retriableDocumentStorageService.js +18 -11
- package/lib/retriableDocumentStorageService.js.map +1 -1
- package/lib/utils.d.ts +23 -1
- package/lib/utils.d.ts.map +1 -1
- package/lib/utils.js +9 -1
- package/lib/utils.js.map +1 -1
- package/package.json +22 -30
- package/src/connectionManager.ts +33 -16
- package/src/container.ts +34 -24
- package/src/containerContext.ts +1 -1
- package/src/containerStorageAdapter.ts +3 -3
- package/src/contracts.ts +4 -4
- package/src/debugLogger.ts +4 -1
- package/src/deltaManager.ts +11 -24
- package/src/index.ts +5 -0
- package/src/loader.ts +8 -8
- package/src/location-redirection-utilities/index.ts +9 -0
- package/src/location-redirection-utilities/resolveWithLocationRedirection.ts +59 -0
- package/src/packageVersion.ts +1 -1
- package/src/retriableDocumentStorageService.ts +29 -15
- package/src/utils.ts +23 -1
package/src/container.ts
CHANGED
|
@@ -40,6 +40,7 @@ import {
|
|
|
40
40
|
IRuntime,
|
|
41
41
|
ReadOnlyInfo,
|
|
42
42
|
isFluidCodeDetails,
|
|
43
|
+
IGetPendingLocalStateProps,
|
|
43
44
|
} from "@fluidframework/container-definitions";
|
|
44
45
|
import {
|
|
45
46
|
IDocumentService,
|
|
@@ -134,6 +135,8 @@ const savedContainerEvent = "saved";
|
|
|
134
135
|
|
|
135
136
|
const packageNotFactoryError = "Code package does not implement IRuntimeFactory";
|
|
136
137
|
|
|
138
|
+
const hasBlobsSummaryTree = ".hasAttachmentBlobs";
|
|
139
|
+
|
|
137
140
|
/**
|
|
138
141
|
* @internal
|
|
139
142
|
*/
|
|
@@ -473,7 +476,11 @@ export class Container
|
|
|
473
476
|
container.mc.logger,
|
|
474
477
|
{ eventName: "RehydrateDetachedFromSnapshot" },
|
|
475
478
|
async (_event) => {
|
|
476
|
-
const deserializedSummary = JSON.parse(snapshot)
|
|
479
|
+
const deserializedSummary = JSON.parse(snapshot);
|
|
480
|
+
if (!isCombinedAppAndProtocolSummary(deserializedSummary, hasBlobsSummaryTree)) {
|
|
481
|
+
throw new UsageError("Cannot rehydrate detached container. Incorrect format");
|
|
482
|
+
}
|
|
483
|
+
|
|
477
484
|
await container.rehydrateDetachedFromSnapshot(deserializedSummary);
|
|
478
485
|
return container;
|
|
479
486
|
},
|
|
@@ -580,6 +587,7 @@ export class Container
|
|
|
580
587
|
private readonly savedOps: ISequencedDocumentMessage[] = [];
|
|
581
588
|
private baseSnapshot?: ISnapshotTree;
|
|
582
589
|
private baseSnapshotBlobs?: ISerializableBlobContents;
|
|
590
|
+
private readonly _containerId: string;
|
|
583
591
|
|
|
584
592
|
private lastVisible: number | undefined;
|
|
585
593
|
private readonly visibilityEventHandler: (() => void) | undefined;
|
|
@@ -815,6 +823,8 @@ export class Container
|
|
|
815
823
|
const clientType = `${interactive ? "interactive" : "noninteractive"}${
|
|
816
824
|
type !== undefined && type !== "" ? `/${type}` : ""
|
|
817
825
|
}`;
|
|
826
|
+
|
|
827
|
+
this._containerId = uuid();
|
|
818
828
|
// Need to use the property getter for docId because for detached flow we don't have the docId initially.
|
|
819
829
|
// We assign the id later so property getter is used.
|
|
820
830
|
this.subLogger = createChildLogger({
|
|
@@ -822,7 +832,7 @@ export class Container
|
|
|
822
832
|
properties: {
|
|
823
833
|
all: {
|
|
824
834
|
clientType, // Differentiating summarizer container from main container
|
|
825
|
-
containerId:
|
|
835
|
+
containerId: this._containerId,
|
|
826
836
|
docId: () => this.resolvedUrl?.id,
|
|
827
837
|
containerAttachState: () => this._attachState,
|
|
828
838
|
containerLifecycleState: () => this._lifecycleState,
|
|
@@ -1103,12 +1113,17 @@ export class Container
|
|
|
1103
1113
|
}
|
|
1104
1114
|
}
|
|
1105
1115
|
|
|
1106
|
-
public async closeAndGetPendingLocalState(
|
|
1116
|
+
public async closeAndGetPendingLocalState(
|
|
1117
|
+
stopBlobAttachingSignal?: AbortSignal,
|
|
1118
|
+
): Promise<string> {
|
|
1107
1119
|
// runtime matches pending ops to successful ones by clientId and client seq num, so we need to close the
|
|
1108
1120
|
// container at the same time we get pending state, otherwise this container could reconnect and resubmit with
|
|
1109
1121
|
// a new clientId and a future container using stale pending state without the new clientId would resubmit them
|
|
1110
1122
|
this.disconnectInternal({ text: "closeAndGetPendingLocalState" }); // TODO https://dev.azure.com/fluidframework/internal/_workitems/edit/5127
|
|
1111
|
-
const pendingState = await this.getPendingLocalStateCore({
|
|
1123
|
+
const pendingState = await this.getPendingLocalStateCore({
|
|
1124
|
+
notifyImminentClosure: true,
|
|
1125
|
+
stopBlobAttachingSignal,
|
|
1126
|
+
});
|
|
1112
1127
|
this.close();
|
|
1113
1128
|
return pendingState;
|
|
1114
1129
|
}
|
|
@@ -1117,7 +1132,7 @@ export class Container
|
|
|
1117
1132
|
return this.getPendingLocalStateCore({ notifyImminentClosure: false });
|
|
1118
1133
|
}
|
|
1119
1134
|
|
|
1120
|
-
private async getPendingLocalStateCore(props:
|
|
1135
|
+
private async getPendingLocalStateCore(props: IGetPendingLocalStateProps) {
|
|
1121
1136
|
return PerformanceEvent.timedExecAsync(
|
|
1122
1137
|
this.mc.logger,
|
|
1123
1138
|
{
|
|
@@ -1178,7 +1193,7 @@ export class Container
|
|
|
1178
1193
|
const combinedSummary = combineAppAndProtocolSummary(appSummary, protocolSummary);
|
|
1179
1194
|
|
|
1180
1195
|
if (this.detachedBlobStorage && this.detachedBlobStorage.size > 0) {
|
|
1181
|
-
combinedSummary.tree[
|
|
1196
|
+
combinedSummary.tree[hasBlobsSummaryTree] = {
|
|
1182
1197
|
type: SummaryType.Blob,
|
|
1183
1198
|
content: "true",
|
|
1184
1199
|
};
|
|
@@ -1264,7 +1279,7 @@ export class Container
|
|
|
1264
1279
|
}, // progress
|
|
1265
1280
|
);
|
|
1266
1281
|
}
|
|
1267
|
-
|
|
1282
|
+
this.storageAdapter.connectToService(this.service);
|
|
1268
1283
|
|
|
1269
1284
|
if (hasAttachmentBlobs) {
|
|
1270
1285
|
// upload blobs to storage
|
|
@@ -1574,14 +1589,7 @@ export class Container
|
|
|
1574
1589
|
this.connectToDeltaStream(connectionArgs);
|
|
1575
1590
|
}
|
|
1576
1591
|
|
|
1577
|
-
|
|
1578
|
-
await this.storageAdapter.connectToService(this.service);
|
|
1579
|
-
} else {
|
|
1580
|
-
// if we have pendingLocalState we can load without storage; don't wait for connection
|
|
1581
|
-
this.storageAdapter.connectToService(this.service).catch((error) => {
|
|
1582
|
-
this.close(error);
|
|
1583
|
-
});
|
|
1584
|
-
}
|
|
1592
|
+
this.storageAdapter.connectToService(this.service);
|
|
1585
1593
|
|
|
1586
1594
|
this._attachState = AttachState.Attached;
|
|
1587
1595
|
|
|
@@ -1815,12 +1823,13 @@ export class Container
|
|
|
1815
1823
|
}
|
|
1816
1824
|
|
|
1817
1825
|
private async rehydrateDetachedFromSnapshot(detachedContainerSnapshot: ISummaryTree) {
|
|
1818
|
-
if (detachedContainerSnapshot.tree[
|
|
1826
|
+
if (detachedContainerSnapshot.tree[hasBlobsSummaryTree] !== undefined) {
|
|
1819
1827
|
assert(
|
|
1820
1828
|
!!this.detachedBlobStorage && this.detachedBlobStorage.size > 0,
|
|
1821
1829
|
0x250 /* "serialized container with attachment blobs must be rehydrated with detached blob storage" */,
|
|
1822
1830
|
);
|
|
1823
|
-
delete
|
|
1831
|
+
// eslint-disable-next-line @typescript-eslint/no-dynamic-delete
|
|
1832
|
+
delete detachedContainerSnapshot.tree[hasBlobsSummaryTree];
|
|
1824
1833
|
}
|
|
1825
1834
|
|
|
1826
1835
|
const snapshotTree = getSnapshotTreeFromSerializedContainer(detachedContainerSnapshot);
|
|
@@ -2007,6 +2016,7 @@ export class Container
|
|
|
2007
2016
|
client.details.environment = [
|
|
2008
2017
|
client.details.environment,
|
|
2009
2018
|
` loaderVersion:${pkgVersion}`,
|
|
2019
|
+
` containerId:${this._containerId}`,
|
|
2010
2020
|
].join(";");
|
|
2011
2021
|
return client;
|
|
2012
2022
|
}
|
|
@@ -2335,8 +2345,8 @@ export class Container
|
|
|
2335
2345
|
this.emit("op", message);
|
|
2336
2346
|
}
|
|
2337
2347
|
|
|
2338
|
-
private submitSignal(
|
|
2339
|
-
this._deltaManager.submitSignal(JSON.stringify(
|
|
2348
|
+
private submitSignal(content: any, targetClientId?: string) {
|
|
2349
|
+
this._deltaManager.submitSignal(JSON.stringify(content), targetClientId);
|
|
2340
2350
|
}
|
|
2341
2351
|
|
|
2342
2352
|
private processSignal(message: ISignalMessage) {
|
|
@@ -2430,7 +2440,7 @@ export class Container
|
|
|
2430
2440
|
this.submitSummaryMessage(summaryOp, referenceSequenceNumber),
|
|
2431
2441
|
(batch: IBatchMessage[], referenceSequenceNumber?: number) =>
|
|
2432
2442
|
this.submitBatch(batch, referenceSequenceNumber),
|
|
2433
|
-
(
|
|
2443
|
+
(content, targetClientId) => this.submitSignal(content, targetClientId),
|
|
2434
2444
|
(error?: ICriticalContainerError) => this.dispose(error),
|
|
2435
2445
|
(error?: ICriticalContainerError) => this.close(error),
|
|
2436
2446
|
this.updateDirtyContainerState,
|
|
@@ -2513,7 +2523,7 @@ export class Container
|
|
|
2513
2523
|
|
|
2514
2524
|
/**
|
|
2515
2525
|
* IContainer interface that includes experimental features still under development.
|
|
2516
|
-
* @
|
|
2526
|
+
* @alpha
|
|
2517
2527
|
*/
|
|
2518
2528
|
export interface IContainerExperimental extends IContainer {
|
|
2519
2529
|
/**
|
|
@@ -2521,7 +2531,7 @@ export interface IContainerExperimental extends IContainer {
|
|
|
2521
2531
|
* submission and potential document corruption. The blob returned MUST be deleted if and when this
|
|
2522
2532
|
* container emits a "connected" event.
|
|
2523
2533
|
* @returns serialized blob that can be passed to Loader.resolve()
|
|
2524
|
-
* @
|
|
2534
|
+
* @alpha misuse of this API can result in duplicate op submission and potential document corruption
|
|
2525
2535
|
* {@link https://github.com/microsoft/FluidFramework/blob/main/packages/loader/container-loader/closeAndGetPendingLocalState.md}
|
|
2526
2536
|
*/
|
|
2527
2537
|
getPendingLocalState?(): Promise<string>;
|
|
@@ -2529,8 +2539,8 @@ export interface IContainerExperimental extends IContainer {
|
|
|
2529
2539
|
/**
|
|
2530
2540
|
* Closes the container and returns serialized local state intended to be
|
|
2531
2541
|
* given to a newly loaded container.
|
|
2532
|
-
* @
|
|
2542
|
+
* @alpha
|
|
2533
2543
|
* {@link https://github.com/microsoft/FluidFramework/blob/main/packages/loader/container-loader/closeAndGetPendingLocalState.md}
|
|
2534
2544
|
*/
|
|
2535
|
-
closeAndGetPendingLocalState?(): Promise<string>;
|
|
2545
|
+
closeAndGetPendingLocalState?(stopBlobAttachingSignal?: AbortSignal): Promise<string>;
|
|
2536
2546
|
}
|
package/src/containerContext.ts
CHANGED
|
@@ -85,7 +85,7 @@ export class ContainerContext implements IContainerContext {
|
|
|
85
85
|
batch: IBatchMessage[],
|
|
86
86
|
referenceSequenceNumber?: number,
|
|
87
87
|
) => number,
|
|
88
|
-
public readonly submitSignalFn: (
|
|
88
|
+
public readonly submitSignalFn: (content: any, targetClientId?: string) => void,
|
|
89
89
|
public readonly disposeFn: (error?: ICriticalContainerError) => void,
|
|
90
90
|
public readonly closeFn: (error?: ICriticalContainerError) => void,
|
|
91
91
|
public readonly updateDirtyContainerState: (dirty: boolean) => void,
|
|
@@ -79,14 +79,14 @@ export class ContainerStorageAdapter implements IDocumentStorageService, IDispos
|
|
|
79
79
|
this.disposed = true;
|
|
80
80
|
}
|
|
81
81
|
|
|
82
|
-
public
|
|
82
|
+
public connectToService(service: IDocumentService): void {
|
|
83
83
|
if (!(this._storageService instanceof BlobOnlyStorage)) {
|
|
84
84
|
return;
|
|
85
85
|
}
|
|
86
86
|
|
|
87
|
-
const
|
|
87
|
+
const storageServiceP = service.connectToStorage();
|
|
88
88
|
const retriableStorage = (this._storageService = new RetriableDocumentStorageService(
|
|
89
|
-
|
|
89
|
+
storageServiceP,
|
|
90
90
|
this.logger,
|
|
91
91
|
));
|
|
92
92
|
|
package/src/contracts.ts
CHANGED
|
@@ -100,7 +100,7 @@ export interface IConnectionManager {
|
|
|
100
100
|
* Submits signal to relay service.
|
|
101
101
|
* Called only when active connection is present.
|
|
102
102
|
*/
|
|
103
|
-
submitSignal(content: any): void;
|
|
103
|
+
submitSignal(content: any, targetClientId?: string): void;
|
|
104
104
|
|
|
105
105
|
/**
|
|
106
106
|
* Submits messages to relay service.
|
|
@@ -133,10 +133,10 @@ export interface IConnectionManagerFactoryArgs {
|
|
|
133
133
|
readonly incomingOpHandler: (messages: ISequencedDocumentMessage[], reason: string) => void;
|
|
134
134
|
|
|
135
135
|
/**
|
|
136
|
-
* Called by connection manager for each incoming
|
|
137
|
-
*
|
|
136
|
+
* Called by connection manager for each incoming signal.
|
|
137
|
+
* May be called before connectHandler is called (due to initial signals on socket connection)
|
|
138
138
|
*/
|
|
139
|
-
readonly signalHandler: (
|
|
139
|
+
readonly signalHandler: (signals: ISignalMessage[]) => void;
|
|
140
140
|
|
|
141
141
|
/**
|
|
142
142
|
* Called when connection manager experiences delay in connecting to relay service.
|
package/src/debugLogger.ts
CHANGED
|
@@ -60,7 +60,10 @@ export class DebugLogger implements ITelemetryBaseLogger {
|
|
|
60
60
|
});
|
|
61
61
|
}
|
|
62
62
|
|
|
63
|
-
private constructor(
|
|
63
|
+
private constructor(
|
|
64
|
+
private readonly debug: IDebugger,
|
|
65
|
+
private readonly debugErr: IDebugger,
|
|
66
|
+
) {}
|
|
64
67
|
|
|
65
68
|
/**
|
|
66
69
|
* Send an event to debug loggers
|
package/src/deltaManager.ts
CHANGED
|
@@ -114,17 +114,6 @@ function isClientMessage(message: ISequencedDocumentMessage | IDocumentMessage):
|
|
|
114
114
|
}
|
|
115
115
|
}
|
|
116
116
|
|
|
117
|
-
/**
|
|
118
|
-
* Type is used to cast AbortController to represent new version of DOM API and prevent build issues
|
|
119
|
-
* TODO: Remove when typescript version of the repo contains the AbortSignal.reason property (AB#5045)
|
|
120
|
-
*/
|
|
121
|
-
type AbortControllerReal = AbortController & { abort(reason?: any): void };
|
|
122
|
-
/**
|
|
123
|
-
* Type is used to cast AbortSignal to represent new version of DOM API and prevent build issues
|
|
124
|
-
* TODO: Remove when typescript version of the repo contains the AbortSignal.reason property (AB#5045)
|
|
125
|
-
*/
|
|
126
|
-
type AbortSignalReal = AbortSignal & { reason: any };
|
|
127
|
-
|
|
128
117
|
/**
|
|
129
118
|
* Manages the flow of both inbound and outbound messages. This class ensures that shared objects receive delta
|
|
130
119
|
* messages in order regardless of possible network conditions or timings causing out of order delivery.
|
|
@@ -312,8 +301,8 @@ export class DeltaManager<TConnectionManager extends IConnectionManager>
|
|
|
312
301
|
return message.clientSequenceNumber;
|
|
313
302
|
}
|
|
314
303
|
|
|
315
|
-
public submitSignal(content: any) {
|
|
316
|
-
return this.connectionManager.submitSignal(content);
|
|
304
|
+
public submitSignal(content: any, targetClientId?: string) {
|
|
305
|
+
return this.connectionManager.submitSignal(content, targetClientId);
|
|
317
306
|
}
|
|
318
307
|
|
|
319
308
|
public flush() {
|
|
@@ -402,7 +391,11 @@ export class DeltaManager<TConnectionManager extends IConnectionManager>
|
|
|
402
391
|
this.close(normalizeError(error));
|
|
403
392
|
}
|
|
404
393
|
},
|
|
405
|
-
signalHandler: (
|
|
394
|
+
signalHandler: (signals: ISignalMessage[]) => {
|
|
395
|
+
for (const signal of signals) {
|
|
396
|
+
this._inboundSignal.push(signal);
|
|
397
|
+
}
|
|
398
|
+
},
|
|
406
399
|
reconnectionDelayHandler: (delayMs: number, error: unknown) =>
|
|
407
400
|
this.emitDelayInfo(this.deltaStreamDelayId, delayMs, error),
|
|
408
401
|
closeHandler: (error: any) => this.close(error),
|
|
@@ -663,8 +656,7 @@ export class DeltaManager<TConnectionManager extends IConnectionManager>
|
|
|
663
656
|
// This is useless for known ranges (to is defined) as it means request is over either way.
|
|
664
657
|
// And it will cancel unbound request too early, not allowing us to learn where the end of the file is.
|
|
665
658
|
if (!opsFromFetch && cancelFetch(op)) {
|
|
666
|
-
|
|
667
|
-
(controller as AbortControllerReal).abort("DeltaManager getDeltas fetch cancelled");
|
|
659
|
+
controller.abort("DeltaManager getDeltas fetch cancelled");
|
|
668
660
|
this._inbound.off("push", opListener);
|
|
669
661
|
}
|
|
670
662
|
};
|
|
@@ -673,10 +665,7 @@ export class DeltaManager<TConnectionManager extends IConnectionManager>
|
|
|
673
665
|
this._inbound.on("push", opListener);
|
|
674
666
|
assert(this.closeAbortController.signal.onabort === null, 0x1e8 /* "reentrancy" */);
|
|
675
667
|
this.closeAbortController.signal.onabort = () =>
|
|
676
|
-
|
|
677
|
-
(controller as AbortControllerReal).abort(
|
|
678
|
-
(this.closeAbortController.signal as AbortSignalReal).reason,
|
|
679
|
-
);
|
|
668
|
+
controller.abort(this.closeAbortController.signal.reason);
|
|
680
669
|
|
|
681
670
|
const stream = this.deltaStorage.fetchMessages(
|
|
682
671
|
from, // inclusive
|
|
@@ -704,8 +693,7 @@ export class DeltaManager<TConnectionManager extends IConnectionManager>
|
|
|
704
693
|
this.logger.sendTelemetryEvent({
|
|
705
694
|
eventName: "DeltaManager_GetDeltasAborted",
|
|
706
695
|
fetchReason,
|
|
707
|
-
|
|
708
|
-
reason: (controller.signal as AbortSignalReal).reason,
|
|
696
|
+
reason: controller.signal.reason,
|
|
709
697
|
});
|
|
710
698
|
}
|
|
711
699
|
this.closeAbortController.signal.onabort = null;
|
|
@@ -762,8 +750,7 @@ export class DeltaManager<TConnectionManager extends IConnectionManager>
|
|
|
762
750
|
}
|
|
763
751
|
|
|
764
752
|
private clearQueues() {
|
|
765
|
-
|
|
766
|
-
(this.closeAbortController as AbortControllerReal).abort("DeltaManager is closed");
|
|
753
|
+
this.closeAbortController.abort("DeltaManager is closed");
|
|
767
754
|
|
|
768
755
|
this._inbound.clear();
|
|
769
756
|
this._inboundSignal.clear();
|
package/src/index.ts
CHANGED
|
@@ -15,4 +15,9 @@ export {
|
|
|
15
15
|
Loader,
|
|
16
16
|
requestResolvedObjectFromContainer,
|
|
17
17
|
} from "./loader";
|
|
18
|
+
export {
|
|
19
|
+
isLocationRedirectionError,
|
|
20
|
+
resolveWithLocationRedirectionHandling,
|
|
21
|
+
} from "./location-redirection-utilities";
|
|
18
22
|
export { IProtocolHandler, ProtocolHandlerBuilder } from "./protocol";
|
|
23
|
+
export { tryParseCompatibleResolvedUrl, IParsedUrl } from "./utils";
|
package/src/loader.ts
CHANGED
|
@@ -41,7 +41,7 @@ import {
|
|
|
41
41
|
} from "@fluidframework/driver-definitions";
|
|
42
42
|
import { IClientDetails } from "@fluidframework/protocol-definitions";
|
|
43
43
|
import { Container, IPendingContainerState } from "./container";
|
|
44
|
-
import { IParsedUrl,
|
|
44
|
+
import { IParsedUrl, tryParseCompatibleResolvedUrl } from "./utils";
|
|
45
45
|
import { pkgVersion } from "./packageVersion";
|
|
46
46
|
import { ProtocolHandlerBuilder } from "./protocol";
|
|
47
47
|
import { DebugLogger } from "./debugLogger";
|
|
@@ -63,7 +63,7 @@ export class RelativeLoader implements ILoader {
|
|
|
63
63
|
) {}
|
|
64
64
|
|
|
65
65
|
/**
|
|
66
|
-
* @deprecated
|
|
66
|
+
* @deprecated Will be removed in future major release. Migrate all usage of IFluidRouter to the Container's IFluidRouter/request.
|
|
67
67
|
*/
|
|
68
68
|
// eslint-disable-next-line import/no-deprecated
|
|
69
69
|
public get IFluidRouter(): IFluidRouter {
|
|
@@ -94,7 +94,7 @@ export class RelativeLoader implements ILoader {
|
|
|
94
94
|
}
|
|
95
95
|
|
|
96
96
|
/**
|
|
97
|
-
* @deprecated
|
|
97
|
+
* @deprecated Will be removed in future major release. Migrate all usage of IFluidRouter to the "entryPoint" pattern. Refer to Removing-IFluidRouter.md
|
|
98
98
|
*/
|
|
99
99
|
public async request(request: IRequest): Promise<IResponse> {
|
|
100
100
|
if (request.url.startsWith("/")) {
|
|
@@ -280,7 +280,7 @@ export async function requestResolvedObjectFromContainer(
|
|
|
280
280
|
headers?: IRequestHeader,
|
|
281
281
|
): Promise<IResponse> {
|
|
282
282
|
ensureResolvedUrlDefined(container.resolvedUrl);
|
|
283
|
-
const parsedUrl =
|
|
283
|
+
const parsedUrl = tryParseCompatibleResolvedUrl(container.resolvedUrl.url);
|
|
284
284
|
|
|
285
285
|
if (parsedUrl === undefined) {
|
|
286
286
|
throw new Error(`Invalid URL ${container.resolvedUrl.url}`);
|
|
@@ -347,7 +347,7 @@ export class Loader implements IHostLoader {
|
|
|
347
347
|
}
|
|
348
348
|
|
|
349
349
|
/**
|
|
350
|
-
* @deprecated
|
|
350
|
+
* @deprecated Will be removed in future major release. Migrate all usage of IFluidRouter to the Container's IFluidRouter/request.
|
|
351
351
|
*/
|
|
352
352
|
// eslint-disable-next-line import/no-deprecated
|
|
353
353
|
public get IFluidRouter(): IFluidRouter {
|
|
@@ -398,7 +398,7 @@ export class Loader implements IHostLoader {
|
|
|
398
398
|
}
|
|
399
399
|
|
|
400
400
|
/**
|
|
401
|
-
* @deprecated
|
|
401
|
+
* @deprecated Will be removed in future major release. Migrate all usage of IFluidRouter to the Container's IFluidRouter/request.
|
|
402
402
|
*/
|
|
403
403
|
public async request(request: IRequest): Promise<IResponse> {
|
|
404
404
|
return PerformanceEvent.timedExecAsync(
|
|
@@ -422,13 +422,13 @@ export class Loader implements IHostLoader {
|
|
|
422
422
|
ensureResolvedUrlDefined(resolvedAsFluid);
|
|
423
423
|
|
|
424
424
|
// Parse URL into data stores
|
|
425
|
-
const parsed =
|
|
425
|
+
const parsed = tryParseCompatibleResolvedUrl(resolvedAsFluid.url);
|
|
426
426
|
if (parsed === undefined) {
|
|
427
427
|
throw new Error(`Invalid URL ${resolvedAsFluid.url}`);
|
|
428
428
|
}
|
|
429
429
|
|
|
430
430
|
if (pendingLocalState !== undefined) {
|
|
431
|
-
const parsedPendingUrl =
|
|
431
|
+
const parsedPendingUrl = tryParseCompatibleResolvedUrl(pendingLocalState.url);
|
|
432
432
|
if (
|
|
433
433
|
parsedPendingUrl?.id !== parsed.id ||
|
|
434
434
|
parsedPendingUrl?.path.replace(/\/$/, "") !== parsed.path.replace(/\/$/, "")
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* Copyright (c) Microsoft Corporation and contributors. All rights reserved.
|
|
3
|
+
* Licensed under the MIT License.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { ITelemetryBaseLogger, IRequest } from "@fluidframework/core-interfaces";
|
|
7
|
+
import {
|
|
8
|
+
DriverErrorTypes,
|
|
9
|
+
ILocationRedirectionError,
|
|
10
|
+
IUrlResolver,
|
|
11
|
+
} from "@fluidframework/driver-definitions";
|
|
12
|
+
import { createChildLogger } from "@fluidframework/telemetry-utils";
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Checks if the error is location redirection error.
|
|
16
|
+
* @param error - error whose type is to be determined.
|
|
17
|
+
* @returns `true` is the error is location redirection error, otherwise `false`.
|
|
18
|
+
*/
|
|
19
|
+
export function isLocationRedirectionError(error: any): error is ILocationRedirectionError {
|
|
20
|
+
return (
|
|
21
|
+
typeof error === "object" &&
|
|
22
|
+
error !== null &&
|
|
23
|
+
error.errorType === DriverErrorTypes.locationRedirection
|
|
24
|
+
);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Handles location redirection while fulfilling the loader request.
|
|
29
|
+
* @param api - Callback in which user can wrap the loader.resolve or loader.request call.
|
|
30
|
+
* @param request - request to be resolved.
|
|
31
|
+
* @param urlResolver - resolver used to resolve the url.
|
|
32
|
+
* @param logger - logger to send events.
|
|
33
|
+
* @returns Response from the API call.
|
|
34
|
+
*/
|
|
35
|
+
export async function resolveWithLocationRedirectionHandling<T>(
|
|
36
|
+
api: (request: IRequest) => Promise<T>,
|
|
37
|
+
request: IRequest,
|
|
38
|
+
urlResolver: IUrlResolver,
|
|
39
|
+
logger?: ITelemetryBaseLogger,
|
|
40
|
+
): Promise<T> {
|
|
41
|
+
let req: IRequest = request;
|
|
42
|
+
const childLogger = createChildLogger({ logger, namespace: "LocationRedirection" });
|
|
43
|
+
for (;;) {
|
|
44
|
+
try {
|
|
45
|
+
return await api(req);
|
|
46
|
+
} catch (error: any) {
|
|
47
|
+
if (!isLocationRedirectionError(error)) {
|
|
48
|
+
throw error;
|
|
49
|
+
}
|
|
50
|
+
childLogger.sendTelemetryEvent({ eventName: "LocationRedirectionError" });
|
|
51
|
+
const resolvedUrl = error.redirectUrl;
|
|
52
|
+
// Generate the new request with new location details from the resolved url. For datastore/relative path,
|
|
53
|
+
// we don't need to pass "/" as host could have asked for a specific data store. So driver need to
|
|
54
|
+
// extract it from the resolved url.
|
|
55
|
+
const absoluteUrl = await urlResolver.getAbsoluteUrl(resolvedUrl, "", undefined);
|
|
56
|
+
req = { url: absoluteUrl, headers: req.headers };
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
package/src/packageVersion.ts
CHANGED
|
@@ -23,13 +23,19 @@ import { runWithRetry } from "@fluidframework/driver-utils";
|
|
|
23
23
|
|
|
24
24
|
export class RetriableDocumentStorageService implements IDocumentStorageService, IDisposable {
|
|
25
25
|
private _disposed = false;
|
|
26
|
+
private internalStorageService: IDocumentStorageService | undefined;
|
|
26
27
|
constructor(
|
|
27
|
-
private readonly
|
|
28
|
+
private readonly internalStorageServiceP: Promise<IDocumentStorageService>,
|
|
28
29
|
private readonly logger: ITelemetryLoggerExt,
|
|
29
|
-
) {
|
|
30
|
+
) {
|
|
31
|
+
this.internalStorageServiceP.then((s) => (this.internalStorageService = s)).catch(() => {});
|
|
32
|
+
}
|
|
30
33
|
|
|
31
34
|
public get policies(): IDocumentStorageServicePolicies | undefined {
|
|
32
|
-
|
|
35
|
+
if (this.internalStorageService) {
|
|
36
|
+
return this.internalStorageService.policies;
|
|
37
|
+
}
|
|
38
|
+
throw new Error("storage service not yet instantiated");
|
|
33
39
|
}
|
|
34
40
|
public get disposed() {
|
|
35
41
|
return this._disposed;
|
|
@@ -39,7 +45,10 @@ export class RetriableDocumentStorageService implements IDocumentStorageService,
|
|
|
39
45
|
}
|
|
40
46
|
|
|
41
47
|
public get repositoryUrl(): string {
|
|
42
|
-
|
|
48
|
+
if (this.internalStorageService) {
|
|
49
|
+
return this.internalStorageService.repositoryUrl;
|
|
50
|
+
}
|
|
51
|
+
throw new Error("storage service not yet instantiated");
|
|
43
52
|
}
|
|
44
53
|
|
|
45
54
|
public async getSnapshotTree(
|
|
@@ -47,14 +56,17 @@ export class RetriableDocumentStorageService implements IDocumentStorageService,
|
|
|
47
56
|
scenarioName?: string,
|
|
48
57
|
): Promise<ISnapshotTree | null> {
|
|
49
58
|
return this.runWithRetry(
|
|
50
|
-
async () =>
|
|
59
|
+
async () =>
|
|
60
|
+
this.internalStorageServiceP.then(async (s) =>
|
|
61
|
+
s.getSnapshotTree(version, scenarioName),
|
|
62
|
+
),
|
|
51
63
|
"storage_getSnapshotTree",
|
|
52
64
|
);
|
|
53
65
|
}
|
|
54
66
|
|
|
55
67
|
public async readBlob(id: string): Promise<ArrayBufferLike> {
|
|
56
68
|
return this.runWithRetry(
|
|
57
|
-
async () => this.
|
|
69
|
+
async () => this.internalStorageServiceP.then(async (s) => s.readBlob(id)),
|
|
58
70
|
"storage_readBlob",
|
|
59
71
|
);
|
|
60
72
|
}
|
|
@@ -67,11 +79,8 @@ export class RetriableDocumentStorageService implements IDocumentStorageService,
|
|
|
67
79
|
): Promise<IVersion[]> {
|
|
68
80
|
return this.runWithRetry(
|
|
69
81
|
async () =>
|
|
70
|
-
this.
|
|
71
|
-
versionId,
|
|
72
|
-
count,
|
|
73
|
-
scenarioName,
|
|
74
|
-
fetchSource,
|
|
82
|
+
this.internalStorageServiceP.then(async (s) =>
|
|
83
|
+
s.getVersions(versionId, count, scenarioName, fetchSource),
|
|
75
84
|
),
|
|
76
85
|
"storage_getVersions",
|
|
77
86
|
);
|
|
@@ -95,26 +104,31 @@ export class RetriableDocumentStorageService implements IDocumentStorageService,
|
|
|
95
104
|
0x251 /* "creation summary has to have seq=0 && handle === undefined" */,
|
|
96
105
|
);
|
|
97
106
|
if (context.referenceSequenceNumber !== 0) {
|
|
98
|
-
return this.
|
|
107
|
+
return this.internalStorageServiceP.then(async (s) =>
|
|
108
|
+
s.uploadSummaryWithContext(summary, context),
|
|
109
|
+
);
|
|
99
110
|
}
|
|
100
111
|
|
|
101
112
|
// Creation flow with attachment blobs - need to do retries!
|
|
102
113
|
return this.runWithRetry(
|
|
103
|
-
async () =>
|
|
114
|
+
async () =>
|
|
115
|
+
this.internalStorageServiceP.then(async (s) =>
|
|
116
|
+
s.uploadSummaryWithContext(summary, context),
|
|
117
|
+
),
|
|
104
118
|
"storage_uploadSummaryWithContext",
|
|
105
119
|
);
|
|
106
120
|
}
|
|
107
121
|
|
|
108
122
|
public async downloadSummary(handle: ISummaryHandle): Promise<ISummaryTree> {
|
|
109
123
|
return this.runWithRetry(
|
|
110
|
-
async () => this.
|
|
124
|
+
async () => this.internalStorageServiceP.then(async (s) => s.downloadSummary(handle)),
|
|
111
125
|
"storage_downloadSummary",
|
|
112
126
|
);
|
|
113
127
|
}
|
|
114
128
|
|
|
115
129
|
public async createBlob(file: ArrayBufferLike): Promise<ICreateBlobResponse> {
|
|
116
130
|
return this.runWithRetry(
|
|
117
|
-
async () => this.
|
|
131
|
+
async () => this.internalStorageServiceP.then(async (s) => s.createBlob(file)),
|
|
118
132
|
"storage_createBlob",
|
|
119
133
|
);
|
|
120
134
|
}
|
package/src/utils.ts
CHANGED
|
@@ -23,9 +23,23 @@ export interface ISnapshotTreeWithBlobContents extends ISnapshotTree {
|
|
|
23
23
|
trees: { [path: string]: ISnapshotTreeWithBlobContents };
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
+
/**
|
|
27
|
+
* Interface to represent the parsed parts of IResolvedUrl.url to help
|
|
28
|
+
* in getting info about different parts of the url.
|
|
29
|
+
* May not be compatible or relevant for any Url Resolver
|
|
30
|
+
*/
|
|
26
31
|
export interface IParsedUrl {
|
|
32
|
+
/**
|
|
33
|
+
* It is combination of tenantid/docId part of the url.
|
|
34
|
+
*/
|
|
27
35
|
id: string;
|
|
36
|
+
/**
|
|
37
|
+
* It is the deep link path in the url.
|
|
38
|
+
*/
|
|
28
39
|
path: string;
|
|
40
|
+
/**
|
|
41
|
+
* Query string part of the url.
|
|
42
|
+
*/
|
|
29
43
|
query: string;
|
|
30
44
|
/**
|
|
31
45
|
* Null means do not use snapshots, undefined means load latest snapshot
|
|
@@ -35,7 +49,15 @@ export interface IParsedUrl {
|
|
|
35
49
|
version: string | null | undefined;
|
|
36
50
|
}
|
|
37
51
|
|
|
38
|
-
|
|
52
|
+
/**
|
|
53
|
+
* Utility api to parse the IResolvedUrl.url into specific parts like querystring, path to get
|
|
54
|
+
* deep link info etc.
|
|
55
|
+
* Warning - This function may not be compatible with any Url Resolver's resolved url. It works
|
|
56
|
+
* with urls of type: protocol://<string>/.../..?<querystring>
|
|
57
|
+
* @param url - This is the IResolvedUrl.url part of the resolved url.
|
|
58
|
+
* @returns The IParsedUrl representing the input URL, or undefined if the format was not supported
|
|
59
|
+
*/
|
|
60
|
+
export function tryParseCompatibleResolvedUrl(url: string): IParsedUrl | undefined {
|
|
39
61
|
const parsed = parse(url, true);
|
|
40
62
|
if (typeof parsed.pathname !== "string") {
|
|
41
63
|
throw new LoggingError("Failed to parse pathname");
|