@fluidframework/container-loader 2.0.0-dev.2.3.0.115467 → 2.0.0-dev.4.1.0.148229
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 +18 -21
- package/.mocharc.js +2 -2
- package/README.md +65 -44
- package/api-extractor.json +2 -2
- package/closeAndGetPendingLocalState.md +51 -0
- package/dist/audience.d.ts +0 -1
- package/dist/audience.d.ts.map +1 -1
- package/dist/audience.js.map +1 -1
- package/dist/catchUpMonitor.d.ts.map +1 -1
- package/dist/catchUpMonitor.js.map +1 -1
- package/dist/collabWindowTracker.d.ts.map +1 -1
- package/dist/collabWindowTracker.js.map +1 -1
- package/dist/connectionManager.d.ts +5 -5
- package/dist/connectionManager.d.ts.map +1 -1
- package/dist/connectionManager.js +107 -44
- package/dist/connectionManager.js.map +1 -1
- package/dist/connectionState.d.ts.map +1 -1
- package/dist/connectionState.js.map +1 -1
- package/dist/connectionStateHandler.d.ts +7 -7
- package/dist/connectionStateHandler.d.ts.map +1 -1
- package/dist/connectionStateHandler.js +50 -21
- package/dist/connectionStateHandler.js.map +1 -1
- package/dist/container.d.ts +64 -5
- package/dist/container.d.ts.map +1 -1
- package/dist/container.js +329 -137
- package/dist/container.js.map +1 -1
- package/dist/containerContext.d.ts +19 -8
- package/dist/containerContext.d.ts.map +1 -1
- package/dist/containerContext.js +58 -14
- package/dist/containerContext.js.map +1 -1
- package/dist/containerStorageAdapter.d.ts +41 -2
- package/dist/containerStorageAdapter.d.ts.map +1 -1
- package/dist/containerStorageAdapter.js +88 -14
- package/dist/containerStorageAdapter.js.map +1 -1
- package/dist/contracts.d.ts +3 -3
- package/dist/contracts.d.ts.map +1 -1
- package/dist/contracts.js.map +1 -1
- package/dist/deltaManager.d.ts +21 -8
- package/dist/deltaManager.d.ts.map +1 -1
- package/dist/deltaManager.js +112 -37
- package/dist/deltaManager.js.map +1 -1
- package/dist/deltaManagerProxy.d.ts +10 -22
- package/dist/deltaManagerProxy.d.ts.map +1 -1
- package/dist/deltaManagerProxy.js +14 -50
- package/dist/deltaManagerProxy.js.map +1 -1
- package/dist/deltaQueue.d.ts.map +1 -1
- package/dist/deltaQueue.js +4 -2
- package/dist/deltaQueue.js.map +1 -1
- package/dist/index.d.ts +4 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -3
- package/dist/index.js.map +1 -1
- package/dist/loader.d.ts +13 -4
- package/dist/loader.d.ts.map +1 -1
- package/dist/loader.js +38 -24
- package/dist/loader.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/protocol.d.ts.map +1 -1
- package/dist/protocol.js +2 -1
- package/dist/protocol.js.map +1 -1
- package/dist/protocolTreeDocumentStorageService.d.ts +6 -2
- package/dist/protocolTreeDocumentStorageService.d.ts.map +1 -1
- package/dist/protocolTreeDocumentStorageService.js +7 -4
- package/dist/protocolTreeDocumentStorageService.js.map +1 -1
- package/dist/quorum.d.ts.map +1 -1
- package/dist/quorum.js.map +1 -1
- package/dist/retriableDocumentStorageService.d.ts.map +1 -1
- package/dist/retriableDocumentStorageService.js +6 -2
- package/dist/retriableDocumentStorageService.js.map +1 -1
- package/dist/utils.d.ts.map +1 -1
- package/dist/utils.js +8 -5
- package/dist/utils.js.map +1 -1
- package/lib/audience.d.ts +0 -1
- package/lib/audience.d.ts.map +1 -1
- package/lib/audience.js.map +1 -1
- package/lib/catchUpMonitor.d.ts.map +1 -1
- package/lib/catchUpMonitor.js.map +1 -1
- package/lib/collabWindowTracker.d.ts.map +1 -1
- package/lib/collabWindowTracker.js.map +1 -1
- package/lib/connectionManager.d.ts +5 -5
- package/lib/connectionManager.d.ts.map +1 -1
- package/lib/connectionManager.js +110 -47
- package/lib/connectionManager.js.map +1 -1
- package/lib/connectionState.d.ts.map +1 -1
- package/lib/connectionState.js.map +1 -1
- package/lib/connectionStateHandler.d.ts +7 -7
- package/lib/connectionStateHandler.d.ts.map +1 -1
- package/lib/connectionStateHandler.js +50 -21
- package/lib/connectionStateHandler.js.map +1 -1
- package/lib/container.d.ts +64 -5
- package/lib/container.d.ts.map +1 -1
- package/lib/container.js +336 -144
- package/lib/container.js.map +1 -1
- package/lib/containerContext.d.ts +19 -8
- package/lib/containerContext.d.ts.map +1 -1
- package/lib/containerContext.js +59 -15
- package/lib/containerContext.js.map +1 -1
- package/lib/containerStorageAdapter.d.ts +41 -2
- package/lib/containerStorageAdapter.d.ts.map +1 -1
- package/lib/containerStorageAdapter.js +86 -14
- package/lib/containerStorageAdapter.js.map +1 -1
- package/lib/contracts.d.ts +3 -3
- package/lib/contracts.d.ts.map +1 -1
- package/lib/contracts.js.map +1 -1
- package/lib/deltaManager.d.ts +21 -8
- package/lib/deltaManager.d.ts.map +1 -1
- package/lib/deltaManager.js +114 -39
- package/lib/deltaManager.js.map +1 -1
- package/lib/deltaManagerProxy.d.ts +10 -22
- package/lib/deltaManagerProxy.d.ts.map +1 -1
- package/lib/deltaManagerProxy.js +14 -50
- package/lib/deltaManagerProxy.js.map +1 -1
- package/lib/deltaQueue.d.ts.map +1 -1
- package/lib/deltaQueue.js +4 -2
- package/lib/deltaQueue.js.map +1 -1
- package/lib/index.d.ts +4 -3
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +2 -2
- package/lib/index.js.map +1 -1
- package/lib/loader.d.ts +13 -4
- package/lib/loader.d.ts.map +1 -1
- package/lib/loader.js +37 -24
- package/lib/loader.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/protocol.d.ts.map +1 -1
- package/lib/protocol.js +2 -1
- package/lib/protocol.js.map +1 -1
- package/lib/protocolTreeDocumentStorageService.d.ts +6 -2
- package/lib/protocolTreeDocumentStorageService.d.ts.map +1 -1
- package/lib/protocolTreeDocumentStorageService.js +7 -4
- package/lib/protocolTreeDocumentStorageService.js.map +1 -1
- package/lib/quorum.d.ts.map +1 -1
- package/lib/quorum.js.map +1 -1
- package/lib/retriableDocumentStorageService.d.ts.map +1 -1
- package/lib/retriableDocumentStorageService.js +6 -2
- package/lib/retriableDocumentStorageService.js.map +1 -1
- package/lib/utils.d.ts.map +1 -1
- package/lib/utils.js +8 -5
- package/lib/utils.js.map +1 -1
- package/package.json +67 -56
- package/prettier.config.cjs +1 -1
- package/src/audience.ts +51 -46
- package/src/catchUpMonitor.ts +39 -37
- package/src/collabWindowTracker.ts +75 -70
- package/src/connectionManager.ts +1040 -941
- package/src/connectionState.ts +19 -19
- package/src/connectionStateHandler.ts +557 -463
- package/src/container.ts +2147 -1784
- package/src/containerContext.ts +417 -345
- package/src/containerStorageAdapter.ts +268 -154
- package/src/contracts.ts +155 -153
- package/src/deltaManager.ts +1074 -945
- package/src/deltaManagerProxy.ts +88 -137
- package/src/deltaQueue.ts +155 -151
- package/src/index.ts +13 -17
- package/src/loader.ts +434 -427
- package/src/packageVersion.ts +1 -1
- package/src/protocol.ts +93 -87
- package/src/protocolTreeDocumentStorageService.ts +34 -34
- package/src/quorum.ts +34 -34
- package/src/retriableDocumentStorageService.ts +118 -102
- package/src/utils.ts +93 -83
- package/tsconfig.esnext.json +6 -6
- package/tsconfig.json +8 -12
package/src/loader.ts
CHANGED
|
@@ -6,129 +6,109 @@
|
|
|
6
6
|
import { v4 as uuid } from "uuid";
|
|
7
7
|
import { ITelemetryBaseLogger, ITelemetryLogger } from "@fluidframework/common-definitions";
|
|
8
8
|
import {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
9
|
+
FluidObject,
|
|
10
|
+
IFluidRouter,
|
|
11
|
+
IRequest,
|
|
12
|
+
IRequestHeader,
|
|
13
|
+
IResponse,
|
|
14
14
|
} from "@fluidframework/core-interfaces";
|
|
15
15
|
import {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
16
|
+
IContainer,
|
|
17
|
+
IFluidModule,
|
|
18
|
+
IHostLoader,
|
|
19
|
+
ILoader,
|
|
20
|
+
ILoaderOptions as ILoaderOptions1,
|
|
21
|
+
LoaderHeader,
|
|
22
|
+
IProvideFluidCodeDetailsComparer,
|
|
23
|
+
IFluidCodeDetails,
|
|
24
24
|
} from "@fluidframework/container-definitions";
|
|
25
25
|
import {
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
26
|
+
ChildLogger,
|
|
27
|
+
DebugLogger,
|
|
28
|
+
IConfigProviderBase,
|
|
29
|
+
loggerToMonitoringContext,
|
|
30
|
+
mixinMonitoringContext,
|
|
31
|
+
MonitoringContext,
|
|
32
|
+
PerformanceEvent,
|
|
33
|
+
sessionStorageConfigProvider,
|
|
34
34
|
} from "@fluidframework/telemetry-utils";
|
|
35
35
|
import {
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
IUrlResolver,
|
|
36
|
+
IDocumentServiceFactory,
|
|
37
|
+
IDocumentStorageService,
|
|
38
|
+
IFluidResolvedUrl,
|
|
39
|
+
IUrlResolver,
|
|
41
40
|
} from "@fluidframework/driver-definitions";
|
|
42
41
|
import { ISequencedDocumentMessage } from "@fluidframework/protocol-definitions";
|
|
43
|
-
import {
|
|
44
|
-
ensureFluidResolvedUrl,
|
|
45
|
-
MultiUrlResolver,
|
|
46
|
-
MultiDocumentServiceFactory,
|
|
47
|
-
} from "@fluidframework/driver-utils";
|
|
42
|
+
import { ensureFluidResolvedUrl } from "@fluidframework/driver-utils";
|
|
48
43
|
import { Container, IPendingContainerState } from "./container";
|
|
49
44
|
import { IParsedUrl, parseUrl } from "./utils";
|
|
50
45
|
import { pkgVersion } from "./packageVersion";
|
|
51
46
|
import { ProtocolHandlerBuilder } from "./protocol";
|
|
52
47
|
|
|
53
48
|
function canUseCache(request: IRequest): boolean {
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
49
|
+
if (request.headers === undefined) {
|
|
50
|
+
return true;
|
|
51
|
+
}
|
|
57
52
|
|
|
58
|
-
|
|
53
|
+
return request.headers[LoaderHeader.cache] !== false;
|
|
59
54
|
}
|
|
60
55
|
|
|
56
|
+
/**
|
|
57
|
+
* @deprecated - In the next release RelativeLoader will no longer be exported. It is an internal class that should not be used directly.
|
|
58
|
+
*/
|
|
61
59
|
export class RelativeLoader implements ILoader {
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
return this.loader.request(request);
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
function createCachedResolver(resolver: IUrlResolver) {
|
|
115
|
-
const cacheResolver = Object.create(resolver) as IUrlResolver;
|
|
116
|
-
const resolveCache = new Map<string, Promise<IResolvedUrl | undefined>>();
|
|
117
|
-
cacheResolver.resolve = async (request: IRequest): Promise<IResolvedUrl | undefined> => {
|
|
118
|
-
if (!canUseCache(request)) {
|
|
119
|
-
return resolver.resolve(request);
|
|
120
|
-
}
|
|
121
|
-
if (!resolveCache.has(request.url)) {
|
|
122
|
-
resolveCache.set(request.url, resolver.resolve(request));
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
return resolveCache.get(request.url);
|
|
126
|
-
};
|
|
127
|
-
return cacheResolver;
|
|
60
|
+
constructor(
|
|
61
|
+
private readonly container: Container,
|
|
62
|
+
private readonly loader: ILoader | undefined,
|
|
63
|
+
) {}
|
|
64
|
+
|
|
65
|
+
public get IFluidRouter(): IFluidRouter {
|
|
66
|
+
return this;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
public async resolve(request: IRequest): Promise<IContainer> {
|
|
70
|
+
if (request.url.startsWith("/")) {
|
|
71
|
+
if (canUseCache(request)) {
|
|
72
|
+
return this.container;
|
|
73
|
+
} else {
|
|
74
|
+
const resolvedUrl = this.container.resolvedUrl;
|
|
75
|
+
ensureFluidResolvedUrl(resolvedUrl);
|
|
76
|
+
const container = await Container.load(this.loader as Loader, {
|
|
77
|
+
canReconnect: request.headers?.[LoaderHeader.reconnect],
|
|
78
|
+
clientDetailsOverride: request.headers?.[LoaderHeader.clientDetails],
|
|
79
|
+
resolvedUrl: { ...resolvedUrl },
|
|
80
|
+
version: request.headers?.[LoaderHeader.version] ?? undefined,
|
|
81
|
+
loadMode: request.headers?.[LoaderHeader.loadMode],
|
|
82
|
+
});
|
|
83
|
+
return container;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
if (this.loader === undefined) {
|
|
88
|
+
throw new Error("Cannot resolve external containers");
|
|
89
|
+
}
|
|
90
|
+
return this.loader.resolve(request);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
public async request(request: IRequest): Promise<IResponse> {
|
|
94
|
+
if (request.url.startsWith("/")) {
|
|
95
|
+
const container = await this.resolve(request);
|
|
96
|
+
return container.request(request);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
if (this.loader === undefined) {
|
|
100
|
+
return {
|
|
101
|
+
status: 404,
|
|
102
|
+
value: "Cannot request external containers",
|
|
103
|
+
mimeType: "plain/text",
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
return this.loader.request(request);
|
|
107
|
+
}
|
|
128
108
|
}
|
|
129
109
|
|
|
130
110
|
export interface ILoaderOptions extends ILoaderOptions1 {
|
|
131
|
-
|
|
111
|
+
summarizeProtocolTree?: boolean;
|
|
132
112
|
}
|
|
133
113
|
|
|
134
114
|
/**
|
|
@@ -138,14 +118,14 @@ export interface ILoaderOptions extends ILoaderOptions1 {
|
|
|
138
118
|
* Encapsulates a module entry point with corresponding code details.
|
|
139
119
|
*/
|
|
140
120
|
export interface IFluidModuleWithDetails {
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
121
|
+
/** Fluid code module that implements the runtime factory needed to instantiate the container runtime. */
|
|
122
|
+
module: IFluidModule;
|
|
123
|
+
/**
|
|
124
|
+
* Code details associated with the module. Represents a document schema this module supports.
|
|
125
|
+
* If the code loader implements the {@link @fluidframework/core-interfaces#IFluidCodeDetailsComparer} interface,
|
|
126
|
+
* it'll be called to determine whether the module code details satisfy the new code proposal in the quorum.
|
|
127
|
+
*/
|
|
128
|
+
details: IFluidCodeDetails;
|
|
149
129
|
}
|
|
150
130
|
|
|
151
131
|
/**
|
|
@@ -154,116 +134,115 @@ export interface IFluidModuleWithDetails {
|
|
|
154
134
|
* Fluid code loader resolves a code module matching the document schema, i.e. code details, such as
|
|
155
135
|
* a package name and package version range.
|
|
156
136
|
*/
|
|
157
|
-
export interface ICodeDetailsLoader
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
load(source: IFluidCodeDetails): Promise<IFluidModuleWithDetails>;
|
|
137
|
+
export interface ICodeDetailsLoader extends Partial<IProvideFluidCodeDetailsComparer> {
|
|
138
|
+
/**
|
|
139
|
+
* Load the code module (package) that is capable to interact with the document.
|
|
140
|
+
*
|
|
141
|
+
* @param source - Code proposal that articulates the current schema the document is written in.
|
|
142
|
+
* @returns - Code module entry point along with the code details associated with it.
|
|
143
|
+
*/
|
|
144
|
+
load(source: IFluidCodeDetails): Promise<IFluidModuleWithDetails>;
|
|
166
145
|
}
|
|
167
146
|
|
|
168
147
|
/**
|
|
169
148
|
* Services and properties necessary for creating a loader
|
|
170
149
|
*/
|
|
171
150
|
export interface ILoaderProps {
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
151
|
+
/**
|
|
152
|
+
* The url resolver used by the loader for resolving external urls
|
|
153
|
+
* into Fluid urls such that the container specified by the
|
|
154
|
+
* external url can be loaded.
|
|
155
|
+
*/
|
|
156
|
+
readonly urlResolver: IUrlResolver;
|
|
157
|
+
/**
|
|
158
|
+
* The document service factory take the Fluid url provided
|
|
159
|
+
* by the resolved url and constructs all the necessary services
|
|
160
|
+
* for communication with the container's server.
|
|
161
|
+
*/
|
|
162
|
+
readonly documentServiceFactory: IDocumentServiceFactory;
|
|
163
|
+
/**
|
|
164
|
+
* The code loader handles loading the necessary code
|
|
165
|
+
* for running a container once it is loaded.
|
|
166
|
+
*/
|
|
167
|
+
readonly codeLoader: ICodeDetailsLoader;
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* A property bag of options used by various layers
|
|
171
|
+
* to control features
|
|
172
|
+
*/
|
|
173
|
+
readonly options?: ILoaderOptions;
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Scope is provided to all container and is a set of shared
|
|
177
|
+
* services for container's to integrate with their host environment.
|
|
178
|
+
*/
|
|
179
|
+
readonly scope?: FluidObject;
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* The logger that all telemetry should be pushed to.
|
|
183
|
+
*/
|
|
184
|
+
readonly logger?: ITelemetryBaseLogger;
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Blobs storage for detached containers.
|
|
188
|
+
*/
|
|
189
|
+
readonly detachedBlobStorage?: IDetachedBlobStorage;
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* The configuration provider which may be used to control features.
|
|
193
|
+
*/
|
|
194
|
+
readonly configProvider?: IConfigProviderBase;
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Optional property for allowing the container to use a custom
|
|
198
|
+
* protocol implementation for handling the quorum and/or the audience.
|
|
199
|
+
*/
|
|
200
|
+
readonly protocolHandlerBuilder?: ProtocolHandlerBuilder;
|
|
222
201
|
}
|
|
223
202
|
|
|
224
203
|
/**
|
|
225
204
|
* Services and properties used by and exposed by the loader
|
|
226
205
|
*/
|
|
227
206
|
export interface ILoaderServices {
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
207
|
+
/**
|
|
208
|
+
* The url resolver used by the loader for resolving external urls
|
|
209
|
+
* into Fluid urls such that the container specified by the
|
|
210
|
+
* external url can be loaded.
|
|
211
|
+
*/
|
|
212
|
+
readonly urlResolver: IUrlResolver;
|
|
213
|
+
/**
|
|
214
|
+
* The document service factory take the Fluid url provided
|
|
215
|
+
* by the resolved url and constructs all the necessary services
|
|
216
|
+
* for communication with the container's server.
|
|
217
|
+
*/
|
|
218
|
+
readonly documentServiceFactory: IDocumentServiceFactory;
|
|
219
|
+
/**
|
|
220
|
+
* The code loader handles loading the necessary code
|
|
221
|
+
* for running a container once it is loaded.
|
|
222
|
+
*/
|
|
223
|
+
readonly codeLoader: ICodeDetailsLoader;
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* A property bag of options used by various layers
|
|
227
|
+
* to control features
|
|
228
|
+
*/
|
|
229
|
+
readonly options: ILoaderOptions;
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* Scope is provided to all container and is a set of shared
|
|
233
|
+
* services for container's to integrate with their host environment.
|
|
234
|
+
*/
|
|
235
|
+
readonly scope: FluidObject;
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* The logger downstream consumers should construct their loggers from
|
|
239
|
+
*/
|
|
240
|
+
readonly subLogger: ITelemetryLogger;
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* Blobs storage for detached containers.
|
|
244
|
+
*/
|
|
245
|
+
readonly detachedBlobStorage?: IDetachedBlobStorage;
|
|
267
246
|
}
|
|
268
247
|
|
|
269
248
|
/**
|
|
@@ -271,228 +250,256 @@ export interface ILoaderServices {
|
|
|
271
250
|
* blobs in detached containers.
|
|
272
251
|
*/
|
|
273
252
|
export type IDetachedBlobStorage = Pick<IDocumentStorageService, "createBlob" | "readBlob"> & {
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
253
|
+
size: number;
|
|
254
|
+
/**
|
|
255
|
+
* Return an array of all blob IDs present in storage
|
|
256
|
+
*/
|
|
257
|
+
getBlobIds(): string[];
|
|
258
|
+
};
|
|
259
|
+
|
|
260
|
+
/**
|
|
261
|
+
* With an already-resolved container, we can request a component directly, without loading the container again
|
|
262
|
+
* @param container - a resolved container
|
|
263
|
+
* @returns component on the container
|
|
264
|
+
*/
|
|
265
|
+
export async function requestResolvedObjectFromContainer(
|
|
266
|
+
container: IContainer,
|
|
267
|
+
headers?: IRequestHeader,
|
|
268
|
+
): Promise<IResponse> {
|
|
269
|
+
ensureFluidResolvedUrl(container.resolvedUrl);
|
|
270
|
+
const parsedUrl = parseUrl(container.resolvedUrl.url);
|
|
271
|
+
|
|
272
|
+
if (parsedUrl === undefined) {
|
|
273
|
+
throw new Error(`Invalid URL ${container.resolvedUrl.url}`);
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
return container.request({
|
|
277
|
+
url: `${parsedUrl.path}${parsedUrl.query}`,
|
|
278
|
+
headers,
|
|
279
|
+
});
|
|
280
|
+
}
|
|
280
281
|
|
|
281
282
|
/**
|
|
282
283
|
* Manages Fluid resource loading
|
|
283
284
|
*/
|
|
284
285
|
export class Loader implements IHostLoader {
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
286
|
+
private readonly containers = new Map<string, Promise<Container>>();
|
|
287
|
+
public readonly services: ILoaderServices;
|
|
288
|
+
private readonly mc: MonitoringContext;
|
|
289
|
+
private readonly protocolHandlerBuilder: ProtocolHandlerBuilder | undefined;
|
|
290
|
+
|
|
291
|
+
constructor(loaderProps: ILoaderProps) {
|
|
292
|
+
const scope: FluidObject<ILoader> = { ...loaderProps.scope };
|
|
293
|
+
if (loaderProps.options?.provideScopeLoader !== false) {
|
|
294
|
+
scope.ILoader = this;
|
|
295
|
+
}
|
|
296
|
+
const telemetryProps = {
|
|
297
|
+
loaderId: uuid(),
|
|
298
|
+
loaderVersion: pkgVersion,
|
|
299
|
+
};
|
|
300
|
+
|
|
301
|
+
const subMc = mixinMonitoringContext(
|
|
302
|
+
DebugLogger.mixinDebugLogger("fluid:telemetry", loaderProps.logger, {
|
|
303
|
+
all: telemetryProps,
|
|
304
|
+
}),
|
|
305
|
+
sessionStorageConfigProvider.value,
|
|
306
|
+
loaderProps.configProvider,
|
|
307
|
+
);
|
|
308
|
+
|
|
309
|
+
this.services = {
|
|
310
|
+
urlResolver: loaderProps.urlResolver,
|
|
311
|
+
documentServiceFactory: loaderProps.documentServiceFactory,
|
|
312
|
+
codeLoader: loaderProps.codeLoader,
|
|
313
|
+
options: loaderProps.options ?? {},
|
|
314
|
+
scope,
|
|
315
|
+
subLogger: subMc.logger,
|
|
316
|
+
detachedBlobStorage: loaderProps.detachedBlobStorage,
|
|
317
|
+
};
|
|
318
|
+
this.mc = loggerToMonitoringContext(ChildLogger.create(this.services.subLogger, "Loader"));
|
|
319
|
+
this.protocolHandlerBuilder = loaderProps.protocolHandlerBuilder;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
public get IFluidRouter(): IFluidRouter {
|
|
323
|
+
return this;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
public async createDetachedContainer(codeDetails: IFluidCodeDetails): Promise<IContainer> {
|
|
327
|
+
const container = await Container.createDetached(
|
|
328
|
+
this,
|
|
329
|
+
codeDetails,
|
|
330
|
+
this.protocolHandlerBuilder,
|
|
331
|
+
);
|
|
332
|
+
|
|
333
|
+
if (this.cachingEnabled) {
|
|
334
|
+
container.once("attached", () => {
|
|
335
|
+
ensureFluidResolvedUrl(container.resolvedUrl);
|
|
336
|
+
const parsedUrl = parseUrl(container.resolvedUrl.url);
|
|
337
|
+
if (parsedUrl !== undefined) {
|
|
338
|
+
this.addToContainerCache(parsedUrl.id, Promise.resolve(container));
|
|
339
|
+
}
|
|
340
|
+
});
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
return container;
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
public async rehydrateDetachedContainerFromSnapshot(snapshot: string): Promise<IContainer> {
|
|
347
|
+
return Container.rehydrateDetachedFromSnapshot(this, snapshot, this.protocolHandlerBuilder);
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
public async resolve(request: IRequest, pendingLocalState?: string): Promise<IContainer> {
|
|
351
|
+
const eventName = pendingLocalState === undefined ? "Resolve" : "ResolveWithPendingState";
|
|
352
|
+
return PerformanceEvent.timedExecAsync(this.mc.logger, { eventName }, async () => {
|
|
353
|
+
const resolved = await this.resolveCore(
|
|
354
|
+
request,
|
|
355
|
+
pendingLocalState !== undefined ? JSON.parse(pendingLocalState) : undefined,
|
|
356
|
+
);
|
|
357
|
+
return resolved.container;
|
|
358
|
+
});
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
public async request(request: IRequest): Promise<IResponse> {
|
|
362
|
+
return PerformanceEvent.timedExecAsync(
|
|
363
|
+
this.mc.logger,
|
|
364
|
+
{ eventName: "Request" },
|
|
365
|
+
async () => {
|
|
366
|
+
const resolved = await this.resolveCore(request);
|
|
367
|
+
return resolved.container.request({
|
|
368
|
+
...request,
|
|
369
|
+
url: `${resolved.parsed.path}${resolved.parsed.query}`,
|
|
370
|
+
});
|
|
371
|
+
},
|
|
372
|
+
);
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
private getKeyForContainerCache(request: IRequest, parsedUrl: IParsedUrl): string {
|
|
376
|
+
const key =
|
|
377
|
+
request.headers?.[LoaderHeader.version] !== undefined
|
|
378
|
+
? `${parsedUrl.id}@${request.headers[LoaderHeader.version]}`
|
|
379
|
+
: parsedUrl.id;
|
|
380
|
+
return key;
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
private addToContainerCache(key: string, containerP: Promise<Container>) {
|
|
384
|
+
this.containers.set(key, containerP);
|
|
385
|
+
containerP
|
|
386
|
+
.then((container) => {
|
|
387
|
+
// If the container is closed or becomes closed after we resolve it, remove it from the cache.
|
|
388
|
+
if (container.closed) {
|
|
389
|
+
this.containers.delete(key);
|
|
390
|
+
} else {
|
|
391
|
+
container.once("closed", () => {
|
|
392
|
+
this.containers.delete(key);
|
|
393
|
+
});
|
|
394
|
+
}
|
|
395
|
+
})
|
|
396
|
+
.catch((error) => {});
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
private async resolveCore(
|
|
400
|
+
request: IRequest,
|
|
401
|
+
pendingLocalState?: IPendingContainerState,
|
|
402
|
+
): Promise<{ container: Container; parsed: IParsedUrl }> {
|
|
403
|
+
const resolvedAsFluid = await this.services.urlResolver.resolve(request);
|
|
404
|
+
ensureFluidResolvedUrl(resolvedAsFluid);
|
|
405
|
+
|
|
406
|
+
// Parse URL into data stores
|
|
407
|
+
const parsed = parseUrl(resolvedAsFluid.url);
|
|
408
|
+
if (parsed === undefined) {
|
|
409
|
+
throw new Error(`Invalid URL ${resolvedAsFluid.url}`);
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
if (pendingLocalState !== undefined) {
|
|
413
|
+
const parsedPendingUrl = parseUrl(pendingLocalState.url);
|
|
414
|
+
if (
|
|
415
|
+
parsedPendingUrl?.id !== parsed.id ||
|
|
416
|
+
parsedPendingUrl?.path.replace(/\/$/, "") !== parsed.path.replace(/\/$/, "")
|
|
417
|
+
) {
|
|
418
|
+
const message = `URL ${resolvedAsFluid.url} does not match pending state URL ${pendingLocalState.url}`;
|
|
419
|
+
throw new Error(message);
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
const { canCache, fromSequenceNumber } = this.parseHeader(parsed, request);
|
|
424
|
+
const shouldCache = pendingLocalState !== undefined ? false : canCache;
|
|
425
|
+
|
|
426
|
+
let container: Container;
|
|
427
|
+
if (shouldCache) {
|
|
428
|
+
const key = this.getKeyForContainerCache(request, parsed);
|
|
429
|
+
const maybeContainer = await this.containers.get(key);
|
|
430
|
+
if (maybeContainer !== undefined) {
|
|
431
|
+
container = maybeContainer;
|
|
432
|
+
} else {
|
|
433
|
+
const containerP = this.loadContainer(request, resolvedAsFluid);
|
|
434
|
+
this.addToContainerCache(key, containerP);
|
|
435
|
+
container = await containerP;
|
|
436
|
+
}
|
|
437
|
+
} else {
|
|
438
|
+
container = await this.loadContainer(request, resolvedAsFluid, pendingLocalState);
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
if (container.deltaManager.lastSequenceNumber <= fromSequenceNumber) {
|
|
442
|
+
await new Promise<void>((resolve, reject) => {
|
|
443
|
+
function opHandler(message: ISequencedDocumentMessage) {
|
|
444
|
+
if (message.sequenceNumber > fromSequenceNumber) {
|
|
445
|
+
resolve();
|
|
446
|
+
container.removeListener("op", opHandler);
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
container.on("op", opHandler);
|
|
451
|
+
});
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
return { container, parsed };
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
private get cachingEnabled() {
|
|
458
|
+
return this.services.options.cache !== false;
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
private canCacheForRequest(headers: IRequestHeader): boolean {
|
|
462
|
+
return this.cachingEnabled && headers[LoaderHeader.cache] !== false;
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
private parseHeader(parsed: IParsedUrl, request: IRequest) {
|
|
466
|
+
let fromSequenceNumber = -1;
|
|
467
|
+
|
|
468
|
+
request.headers = request.headers ?? {};
|
|
469
|
+
|
|
470
|
+
const headerSeqNum = request.headers[LoaderHeader.sequenceNumber];
|
|
471
|
+
if (headerSeqNum !== undefined) {
|
|
472
|
+
fromSequenceNumber = headerSeqNum;
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
// If set in both query string and headers, use query string
|
|
476
|
+
request.headers[LoaderHeader.version] =
|
|
477
|
+
parsed.version ?? request.headers[LoaderHeader.version];
|
|
478
|
+
|
|
479
|
+
const canCache = this.canCacheForRequest(request.headers);
|
|
480
|
+
|
|
481
|
+
return {
|
|
482
|
+
canCache,
|
|
483
|
+
fromSequenceNumber,
|
|
484
|
+
};
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
private async loadContainer(
|
|
488
|
+
request: IRequest,
|
|
489
|
+
resolved: IFluidResolvedUrl,
|
|
490
|
+
pendingLocalState?: IPendingContainerState,
|
|
491
|
+
): Promise<Container> {
|
|
492
|
+
return Container.load(
|
|
493
|
+
this,
|
|
494
|
+
{
|
|
495
|
+
canReconnect: request.headers?.[LoaderHeader.reconnect],
|
|
496
|
+
clientDetailsOverride: request.headers?.[LoaderHeader.clientDetails],
|
|
497
|
+
resolvedUrl: resolved,
|
|
498
|
+
version: request.headers?.[LoaderHeader.version] ?? undefined,
|
|
499
|
+
loadMode: request.headers?.[LoaderHeader.loadMode],
|
|
500
|
+
},
|
|
501
|
+
pendingLocalState,
|
|
502
|
+
this.protocolHandlerBuilder,
|
|
503
|
+
);
|
|
504
|
+
}
|
|
498
505
|
}
|