@fluidframework/odsp-driver 1.1.0 → 1.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/dist/checkUrl.d.ts +1 -1
- package/dist/checkUrl.js +1 -1
- package/dist/checkUrl.js.map +1 -1
- package/dist/constants.d.ts +1 -1
- package/dist/constants.d.ts.map +1 -1
- package/dist/constants.js +1 -1
- package/dist/constants.js.map +1 -1
- package/dist/contractsPublic.d.ts +1 -0
- package/dist/contractsPublic.d.ts.map +1 -1
- package/dist/contractsPublic.js.map +1 -1
- package/dist/getFileLink.js +0 -4
- package/dist/getFileLink.js.map +1 -1
- package/dist/localOdspDriver/localOdspDocumentService.d.ts +26 -0
- package/dist/localOdspDriver/localOdspDocumentService.d.ts.map +1 -0
- package/dist/localOdspDriver/localOdspDocumentService.js +39 -0
- package/dist/localOdspDriver/localOdspDocumentService.js.map +1 -0
- package/dist/localOdspDriver/localOdspDocumentServiceFactory.d.ts +24 -0
- package/dist/localOdspDriver/localOdspDocumentServiceFactory.d.ts.map +1 -0
- package/dist/localOdspDriver/localOdspDocumentServiceFactory.js +45 -0
- package/dist/localOdspDriver/localOdspDocumentServiceFactory.js.map +1 -0
- package/dist/localOdspDriver/localOdspDocumentStorageManager.d.ts +27 -0
- package/dist/localOdspDriver/localOdspDocumentStorageManager.d.ts.map +1 -0
- package/dist/localOdspDriver/localOdspDocumentStorageManager.js +66 -0
- package/dist/localOdspDriver/localOdspDocumentStorageManager.js.map +1 -0
- package/dist/odspDeltaStorageService.js.map +1 -1
- package/dist/odspDocumentService.d.ts.map +1 -1
- package/dist/odspDocumentService.js.map +1 -1
- package/dist/odspDocumentServiceFactory.d.ts +2 -1
- package/dist/odspDocumentServiceFactory.d.ts.map +1 -1
- package/dist/odspDocumentServiceFactory.js +7 -1
- package/dist/odspDocumentServiceFactory.js.map +1 -1
- package/dist/odspDocumentServiceFactoryCore.d.ts +3 -1
- package/dist/odspDocumentServiceFactoryCore.d.ts.map +1 -1
- package/dist/odspDocumentServiceFactoryCore.js.map +1 -1
- package/dist/odspDocumentStorageManager.d.ts +5 -23
- package/dist/odspDocumentStorageManager.d.ts.map +1 -1
- package/dist/odspDocumentStorageManager.js +52 -245
- package/dist/odspDocumentStorageManager.js.map +1 -1
- package/dist/odspDocumentStorageServiceBase.d.ts +58 -0
- package/dist/odspDocumentStorageServiceBase.d.ts.map +1 -0
- package/dist/odspDocumentStorageServiceBase.js +216 -0
- package/dist/odspDocumentStorageServiceBase.js.map +1 -0
- package/dist/odspDriverUrlResolver.js +1 -1
- package/dist/odspDriverUrlResolver.js.map +1 -1
- package/dist/odspDriverUrlResolverForShareLink.d.ts +8 -2
- package/dist/odspDriverUrlResolverForShareLink.d.ts.map +1 -1
- package/dist/odspDriverUrlResolverForShareLink.js +11 -3
- package/dist/odspDriverUrlResolverForShareLink.js.map +1 -1
- package/dist/odspFluidFileLink.d.ts.map +1 -1
- package/dist/odspFluidFileLink.js +27 -21
- package/dist/odspFluidFileLink.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/lib/checkUrl.d.ts +1 -1
- package/lib/checkUrl.js +1 -1
- package/lib/checkUrl.js.map +1 -1
- package/lib/constants.d.ts +1 -1
- package/lib/constants.d.ts.map +1 -1
- package/lib/constants.js +1 -1
- package/lib/constants.js.map +1 -1
- package/lib/contractsPublic.d.ts +1 -0
- package/lib/contractsPublic.d.ts.map +1 -1
- package/lib/contractsPublic.js.map +1 -1
- package/lib/getFileLink.js +0 -4
- package/lib/getFileLink.js.map +1 -1
- package/lib/localOdspDriver/localOdspDocumentService.d.ts +26 -0
- package/lib/localOdspDriver/localOdspDocumentService.d.ts.map +1 -0
- package/lib/localOdspDriver/localOdspDocumentService.js +35 -0
- package/lib/localOdspDriver/localOdspDocumentService.js.map +1 -0
- package/lib/localOdspDriver/localOdspDocumentServiceFactory.d.ts +24 -0
- package/lib/localOdspDriver/localOdspDocumentServiceFactory.d.ts.map +1 -0
- package/lib/localOdspDriver/localOdspDocumentServiceFactory.js +41 -0
- package/lib/localOdspDriver/localOdspDocumentServiceFactory.js.map +1 -0
- package/lib/localOdspDriver/localOdspDocumentStorageManager.d.ts +27 -0
- package/lib/localOdspDriver/localOdspDocumentStorageManager.d.ts.map +1 -0
- package/lib/localOdspDriver/localOdspDocumentStorageManager.js +62 -0
- package/lib/localOdspDriver/localOdspDocumentStorageManager.js.map +1 -0
- package/lib/odspDeltaStorageService.js.map +1 -1
- package/lib/odspDocumentService.d.ts.map +1 -1
- package/lib/odspDocumentService.js +1 -1
- package/lib/odspDocumentService.js.map +1 -1
- package/lib/odspDocumentServiceFactory.d.ts +2 -1
- package/lib/odspDocumentServiceFactory.d.ts.map +1 -1
- package/lib/odspDocumentServiceFactory.js +5 -0
- package/lib/odspDocumentServiceFactory.js.map +1 -1
- package/lib/odspDocumentServiceFactoryCore.d.ts +3 -1
- package/lib/odspDocumentServiceFactoryCore.d.ts.map +1 -1
- package/lib/odspDocumentServiceFactoryCore.js.map +1 -1
- package/lib/odspDocumentStorageManager.d.ts +5 -23
- package/lib/odspDocumentStorageManager.d.ts.map +1 -1
- package/lib/odspDocumentStorageManager.js +53 -246
- package/lib/odspDocumentStorageManager.js.map +1 -1
- package/lib/odspDocumentStorageServiceBase.d.ts +58 -0
- package/lib/odspDocumentStorageServiceBase.d.ts.map +1 -0
- package/lib/odspDocumentStorageServiceBase.js +212 -0
- package/lib/odspDocumentStorageServiceBase.js.map +1 -0
- package/lib/odspDriverUrlResolver.js +1 -1
- package/lib/odspDriverUrlResolver.js.map +1 -1
- package/lib/odspDriverUrlResolverForShareLink.d.ts +8 -2
- package/lib/odspDriverUrlResolverForShareLink.d.ts.map +1 -1
- package/lib/odspDriverUrlResolverForShareLink.js +11 -3
- package/lib/odspDriverUrlResolverForShareLink.js.map +1 -1
- package/lib/odspFluidFileLink.d.ts.map +1 -1
- package/lib/odspFluidFileLink.js +27 -21
- package/lib/odspFluidFileLink.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/package.json +17 -12
- package/src/checkUrl.ts +1 -1
- package/src/constants.ts +1 -1
- package/src/contractsPublic.ts +1 -0
- package/src/getFileLink.ts +0 -5
- package/src/localOdspDriver/localOdspDocumentService.ts +54 -0
- package/src/localOdspDriver/localOdspDocumentServiceFactory.ts +67 -0
- package/src/localOdspDriver/localOdspDocumentStorageManager.ts +83 -0
- package/src/odspDeltaStorageService.ts +1 -1
- package/src/odspDocumentService.ts +5 -1
- package/src/odspDocumentServiceFactory.ts +7 -3
- package/src/odspDocumentServiceFactoryCore.ts +1 -1
- package/src/odspDocumentStorageManager.ts +81 -312
- package/src/odspDocumentStorageServiceBase.ts +268 -0
- package/src/odspDriverUrlResolver.ts +1 -1
- package/src/odspDriverUrlResolverForShareLink.ts +13 -1
- package/src/odspFluidFileLink.ts +26 -22
- package/src/packageVersion.ts +1 -1
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* Copyright (c) Microsoft Corporation and contributors. All rights reserved.
|
|
3
|
+
* Licensed under the MIT License.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { assert } from "@fluidframework/common-utils";
|
|
7
|
+
import { IDocumentStorageService, ISummaryContext, LoaderCachingPolicy } from "@fluidframework/driver-definitions";
|
|
8
|
+
import * as api from "@fluidframework/protocol-definitions";
|
|
9
|
+
import { defaultCacheExpiryTimeoutMs } from "./epochTracker";
|
|
10
|
+
import { ISnapshotContents } from "./odspPublicUtils";
|
|
11
|
+
|
|
12
|
+
/* eslint-disable max-len */
|
|
13
|
+
|
|
14
|
+
class BlobCache {
|
|
15
|
+
// Save the timeout so we can cancel and reschedule it as needed
|
|
16
|
+
private blobCacheTimeout: ReturnType<typeof setTimeout> | undefined;
|
|
17
|
+
// If the defer flag is set when the timeout fires, we'll reschedule rather than clear immediately
|
|
18
|
+
// This deferral approach is used (rather than clearing/resetting the timer) as current calling patterns trigger
|
|
19
|
+
// too many calls to setTimeout/clearTimeout.
|
|
20
|
+
private deferBlobCacheClear: boolean = false;
|
|
21
|
+
|
|
22
|
+
private readonly _blobCache: Map<string, ArrayBuffer> = new Map();
|
|
23
|
+
|
|
24
|
+
// Tracks all blob IDs evicted from cache
|
|
25
|
+
private readonly blobsEvicted: Set<string> = new Set();
|
|
26
|
+
|
|
27
|
+
// Initial time-out to purge data from cache
|
|
28
|
+
// If this time out is very small, then we purge blobs from cache too soon and that results in a lot of
|
|
29
|
+
// requests to storage, which brings down perf and may trip protection limits causing 429s
|
|
30
|
+
private blobCacheTimeoutDuration = 2 * 60 * 1000;
|
|
31
|
+
|
|
32
|
+
// SPO does not keep old snapshots around for long, so we are running chances of not
|
|
33
|
+
// being able to rehydrate data store / DDS in the future if we purge anything (and with blob de-duping,
|
|
34
|
+
// even if blob read by runtime, it could be read again in the future)
|
|
35
|
+
// So for now, purging is disabled.
|
|
36
|
+
private readonly purgeEnabled = false;
|
|
37
|
+
|
|
38
|
+
public get value() {
|
|
39
|
+
return this._blobCache;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
public addBlobs(blobs: Map<string, ArrayBuffer>) {
|
|
43
|
+
blobs.forEach((value, blobId) => {
|
|
44
|
+
this._blobCache.set(blobId, value);
|
|
45
|
+
});
|
|
46
|
+
// Reset the timer on cache set
|
|
47
|
+
this.scheduleClearBlobsCache();
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Schedule a timer for clearing the blob cache or defer the current one.
|
|
52
|
+
*/
|
|
53
|
+
private scheduleClearBlobsCache() {
|
|
54
|
+
if (this.blobCacheTimeout !== undefined) {
|
|
55
|
+
// If we already have an outstanding timer, just signal that we should defer the clear
|
|
56
|
+
this.deferBlobCacheClear = true;
|
|
57
|
+
} else if (this.purgeEnabled) {
|
|
58
|
+
// If we don't have an outstanding timer, set a timer
|
|
59
|
+
// When the timer runs out, we'll decide whether to proceed with the cache clear or reset the timer
|
|
60
|
+
const clearCacheOrDefer = () => {
|
|
61
|
+
this.blobCacheTimeout = undefined;
|
|
62
|
+
if (this.deferBlobCacheClear) {
|
|
63
|
+
this.deferBlobCacheClear = false;
|
|
64
|
+
this.scheduleClearBlobsCache();
|
|
65
|
+
} else {
|
|
66
|
+
// NOTE: Slightly better algorithm here would be to purge either only big blobs,
|
|
67
|
+
// or sort them by size and purge enough big blobs to leave only 256Kb of small blobs in cache
|
|
68
|
+
// Purging is optimizing memory footprint. But count controls potential number of storage requests
|
|
69
|
+
// We want to optimize both - memory footprint and number of future requests to storage.
|
|
70
|
+
// Note that Container can realize data store or DDS on-demand at any point in time, so we do not
|
|
71
|
+
// control when blobs will be used.
|
|
72
|
+
this._blobCache.forEach((_, blobId) => this.blobsEvicted.add(blobId));
|
|
73
|
+
this._blobCache.clear();
|
|
74
|
+
}
|
|
75
|
+
};
|
|
76
|
+
this.blobCacheTimeout = setTimeout(clearCacheOrDefer, this.blobCacheTimeoutDuration);
|
|
77
|
+
// any future storage reads that get into the cache should be cleared from cache rather quickly -
|
|
78
|
+
// there is not much value in keeping them longer
|
|
79
|
+
this.blobCacheTimeoutDuration = 10 * 1000;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
public getBlob(blobId: string) {
|
|
84
|
+
// Reset the timer on attempted cache read
|
|
85
|
+
this.scheduleClearBlobsCache();
|
|
86
|
+
const blobContent = this._blobCache.get(blobId);
|
|
87
|
+
const evicted = this.blobsEvicted.has(blobId);
|
|
88
|
+
return { blobContent, evicted };
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
public setBlob(blobId: string, blob: ArrayBuffer) {
|
|
92
|
+
// This API is called as result of cache miss and reading blob from storage.
|
|
93
|
+
// Runtime never reads same blob twice.
|
|
94
|
+
// The only reason we may get read request for same blob is blob de-duping in summaries.
|
|
95
|
+
// Note that the bigger the size, the less likely blobs are the same, so there is very little benefit of caching big blobs.
|
|
96
|
+
// Images are the only exception - user may insert same image twice. But we currently do not de-dup them - only snapshot
|
|
97
|
+
// blobs are de-duped.
|
|
98
|
+
const size = blob.byteLength;
|
|
99
|
+
if (size < 256 * 1024) {
|
|
100
|
+
// Reset the timer on cache set
|
|
101
|
+
this.scheduleClearBlobsCache();
|
|
102
|
+
return this._blobCache.set(blobId, blob);
|
|
103
|
+
} else {
|
|
104
|
+
// we evicted it here by not caching.
|
|
105
|
+
this.blobsEvicted.add(blobId);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export abstract class OdspDocumentStorageServiceBase implements IDocumentStorageService {
|
|
111
|
+
readonly policies = {
|
|
112
|
+
// By default, ODSP tells the container not to prefetch/cache.
|
|
113
|
+
caching: LoaderCachingPolicy.NoCaching,
|
|
114
|
+
|
|
115
|
+
// ODSP storage works better if it has less number of blobs / edges
|
|
116
|
+
// Runtime creating many small blobs results in sub-optimal perf.
|
|
117
|
+
// 2K seems like the sweat spot:
|
|
118
|
+
// The smaller the number, less blobs we aggregate. Most storages are very likely to have notion
|
|
119
|
+
// of minimal "cluster" size, so having small blobs is wasteful
|
|
120
|
+
// At the same time increasing the limit ensure that more blobs with user content are aggregated,
|
|
121
|
+
// reducing possibility for de-duping of same blobs (i.e. .attributes rolled into aggregate blob
|
|
122
|
+
// are not reused across data stores, or even within data store, resulting in duplication of content)
|
|
123
|
+
// Note that duplication of content should not have significant impact for bytes over wire as
|
|
124
|
+
// compression of http payload mostly takes care of it, but it does impact storage size and in-memory sizes.
|
|
125
|
+
minBlobSize: 2048,
|
|
126
|
+
maximumCacheDurationMs: defaultCacheExpiryTimeoutMs,
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
protected readonly commitCache: Map<string, api.ISnapshotTree> = new Map();
|
|
130
|
+
|
|
131
|
+
private readonly attributesBlobHandles: Set<string> = new Set();
|
|
132
|
+
|
|
133
|
+
private _ops: api.ISequencedDocumentMessage[] | undefined;
|
|
134
|
+
|
|
135
|
+
private _snapshotSequenceNumber: number | undefined;
|
|
136
|
+
|
|
137
|
+
protected readonly blobCache = new BlobCache();
|
|
138
|
+
|
|
139
|
+
public set ops(ops: api.ISequencedDocumentMessage[] | undefined) {
|
|
140
|
+
assert(this._ops === undefined, 0x0a5 /* "Trying to set ops when they are already set!" */);
|
|
141
|
+
this._ops = ops;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
public get ops(): api.ISequencedDocumentMessage[] | undefined {
|
|
145
|
+
return this._ops;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
public get snapshotSequenceNumber() {
|
|
149
|
+
return this._snapshotSequenceNumber;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
public get repositoryUrl(): string {
|
|
153
|
+
return "";
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
public abstract createBlob(file: ArrayBufferLike): Promise<api.ICreateBlobResponse>;
|
|
157
|
+
|
|
158
|
+
private async readBlobCore(blobId: string): Promise<ArrayBuffer> {
|
|
159
|
+
const { blobContent, evicted } = this.blobCache.getBlob(blobId);
|
|
160
|
+
return blobContent ?? this.fetchBlobFromStorage(blobId, evicted);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
protected abstract fetchBlobFromStorage(blobId: string, evicted: boolean): Promise<ArrayBuffer>;
|
|
164
|
+
|
|
165
|
+
public async readBlob(blobId: string): Promise<ArrayBufferLike> {
|
|
166
|
+
return this.readBlobCore(blobId);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
public async getSnapshotTree(version?: api.IVersion, scenarioName?: string): Promise<api.ISnapshotTree | null> {
|
|
170
|
+
let id: string;
|
|
171
|
+
if (!version || !version.id) {
|
|
172
|
+
const versions = await this.getVersions(null, 1, scenarioName);
|
|
173
|
+
if (!versions || versions.length === 0) {
|
|
174
|
+
return null;
|
|
175
|
+
}
|
|
176
|
+
id = versions[0].id;
|
|
177
|
+
} else {
|
|
178
|
+
id = version.id;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
const snapshotTree = await this.readTree(id, scenarioName);
|
|
182
|
+
if (!snapshotTree) {
|
|
183
|
+
return null;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
if (snapshotTree.blobs) {
|
|
187
|
+
const attributesBlob = snapshotTree.blobs.attributes;
|
|
188
|
+
if (attributesBlob) {
|
|
189
|
+
this.attributesBlobHandles.add(attributesBlob);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// When we upload the container snapshot, we upload appTree in ".app" and protocol tree in ".protocol"
|
|
194
|
+
// So when we request the snapshot we get ".app" as tree and not as commit node as in the case just above.
|
|
195
|
+
const appTree = snapshotTree.trees[".app"];
|
|
196
|
+
const protocolTree = snapshotTree.trees[".protocol"];
|
|
197
|
+
|
|
198
|
+
return this.combineProtocolAndAppSnapshotTree(appTree, protocolTree);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
public abstract getVersions(blobid: string | null, count: number, scenarioName?: string): Promise<api.IVersion[]>;
|
|
202
|
+
|
|
203
|
+
public abstract uploadSummaryWithContext(summary: api.ISummaryTree, context: ISummaryContext): Promise<string>;
|
|
204
|
+
|
|
205
|
+
public async downloadSummary(commit: api.ISummaryHandle): Promise<api.ISummaryTree> {
|
|
206
|
+
throw new Error("Not implemented yet");
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
protected setRootTree(id: string, tree: api.ISnapshotTree) {
|
|
210
|
+
this.commitCache.set(id, tree);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
protected initBlobsCache(blobs: Map<string, ArrayBuffer>) {
|
|
214
|
+
this.blobCache.addBlobs(blobs);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
private async readTree(id: string, scenarioName?: string): Promise<api.ISnapshotTree | null> {
|
|
218
|
+
let tree = this.commitCache.get(id);
|
|
219
|
+
if (!tree) {
|
|
220
|
+
tree = await this.fetchTreeFromSnapshot(id, scenarioName);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
return tree ?? null;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
protected abstract fetchTreeFromSnapshot(id: string, scenarioName?: string): Promise<api.ISnapshotTree | undefined>;
|
|
227
|
+
|
|
228
|
+
private combineProtocolAndAppSnapshotTree(
|
|
229
|
+
hierarchicalAppTree: api.ISnapshotTree,
|
|
230
|
+
hierarchicalProtocolTree: api.ISnapshotTree,
|
|
231
|
+
) {
|
|
232
|
+
const summarySnapshotTree: api.ISnapshotTree = {
|
|
233
|
+
blobs: {
|
|
234
|
+
...hierarchicalAppTree.blobs,
|
|
235
|
+
},
|
|
236
|
+
trees: {
|
|
237
|
+
...hierarchicalAppTree.trees,
|
|
238
|
+
// the app tree could have a .protocol
|
|
239
|
+
// in that case we want to server protocol to override it
|
|
240
|
+
".protocol": hierarchicalProtocolTree,
|
|
241
|
+
},
|
|
242
|
+
};
|
|
243
|
+
|
|
244
|
+
return summarySnapshotTree;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
protected initializeFromSnapshot(odspSnapshotCacheValue: ISnapshotContents): string | undefined {
|
|
248
|
+
this._snapshotSequenceNumber = odspSnapshotCacheValue.sequenceNumber;
|
|
249
|
+
const { snapshotTree, blobs, ops } = odspSnapshotCacheValue;
|
|
250
|
+
|
|
251
|
+
// id should be undefined in case of just ops in snapshot.
|
|
252
|
+
let id: string | undefined;
|
|
253
|
+
if (snapshotTree) {
|
|
254
|
+
id = snapshotTree.id;
|
|
255
|
+
assert(id !== undefined, 0x221 /* "Root tree should contain the id" */);
|
|
256
|
+
this.setRootTree(id, snapshotTree);
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
if (blobs) {
|
|
260
|
+
this.initBlobsCache(blobs);
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
this.ops = ops;
|
|
264
|
+
return id;
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
/* eslint-enable max-len */
|
|
@@ -108,7 +108,7 @@ export class OdspDriverUrlResolver implements IUrlResolver {
|
|
|
108
108
|
fileName,
|
|
109
109
|
summarizer: false,
|
|
110
110
|
codeHint: {
|
|
111
|
-
containerPackageName: packageName
|
|
111
|
+
containerPackageName: packageName ?? undefined,
|
|
112
112
|
},
|
|
113
113
|
fileVersion: undefined,
|
|
114
114
|
shareLinkInfo,
|
|
@@ -61,11 +61,15 @@ export class OdspDriverUrlResolverForShareLink implements IUrlResolver {
|
|
|
61
61
|
* @param appName - application name hint that is encoded with url produced by getAbsoluteUrl() method.
|
|
62
62
|
* This hint is used by link handling logic which determines which app to redirect to when user
|
|
63
63
|
* navigates directly to the link.
|
|
64
|
+
* @param getContext - callback function which is used to get context for given resolved url. If context
|
|
65
|
+
* is returned then it will be embedded into url returned by getAbsoluteUrl() method.
|
|
64
66
|
*/
|
|
65
67
|
public constructor(
|
|
66
68
|
shareLinkFetcherProps?: ShareLinkFetcherProps | undefined,
|
|
67
69
|
logger?: ITelemetryBaseLogger,
|
|
68
70
|
private readonly appName?: string,
|
|
71
|
+
private readonly getContext?:
|
|
72
|
+
(resolvedUrl: IOdspResolvedUrl, dataStorePath: string) => Promise<string | undefined>,
|
|
69
73
|
) {
|
|
70
74
|
this.logger = createOdspLogger(logger);
|
|
71
75
|
if (shareLinkFetcherProps) {
|
|
@@ -185,7 +189,9 @@ export class OdspDriverUrlResolverForShareLink implements IUrlResolver {
|
|
|
185
189
|
* Requests a driver + data store storage URL. Note that this method requires share link to be fetched
|
|
186
190
|
* and it will throw in case share link fetcher props were not specified when instance was created.
|
|
187
191
|
* @param resolvedUrl - The driver resolved URL
|
|
188
|
-
* @param
|
|
192
|
+
* @param dataStorePath - The relative data store path URL.
|
|
193
|
+
* For requesting a driver URL, this value should always be '/'
|
|
194
|
+
* @param packageInfoSource - optional, represents container package information to be included in url.
|
|
189
195
|
*/
|
|
190
196
|
public async getAbsoluteUrl(
|
|
191
197
|
resolvedUrl: IResolvedUrl,
|
|
@@ -193,8 +199,10 @@ export class OdspDriverUrlResolverForShareLink implements IUrlResolver {
|
|
|
193
199
|
packageInfoSource?: IContainerPackageInfo,
|
|
194
200
|
): Promise<string> {
|
|
195
201
|
const odspResolvedUrl = getOdspResolvedUrl(resolvedUrl);
|
|
202
|
+
|
|
196
203
|
const shareLink = await this.getShareLinkPromise(odspResolvedUrl);
|
|
197
204
|
const shareLinkUrl = new URL(shareLink);
|
|
205
|
+
|
|
198
206
|
// back-compat: GitHub #9653
|
|
199
207
|
const isFluidPackage = (pkg: any) =>
|
|
200
208
|
typeof pkg === "object"
|
|
@@ -211,6 +219,8 @@ export class OdspDriverUrlResolverForShareLink implements IUrlResolver {
|
|
|
211
219
|
}
|
|
212
220
|
containerPackageName = containerPackageName ?? odspResolvedUrl.codeHint?.containerPackageName;
|
|
213
221
|
|
|
222
|
+
const context = await this.getContext?.(odspResolvedUrl, dataStorePath);
|
|
223
|
+
|
|
214
224
|
storeLocatorInOdspUrl(shareLinkUrl, {
|
|
215
225
|
siteUrl: odspResolvedUrl.siteUrl,
|
|
216
226
|
driveId: odspResolvedUrl.driveId,
|
|
@@ -219,6 +229,7 @@ export class OdspDriverUrlResolverForShareLink implements IUrlResolver {
|
|
|
219
229
|
appName: this.appName,
|
|
220
230
|
containerPackageName,
|
|
221
231
|
fileVersion: odspResolvedUrl.fileVersion,
|
|
232
|
+
context,
|
|
222
233
|
});
|
|
223
234
|
|
|
224
235
|
return shareLinkUrl.href;
|
|
@@ -237,6 +248,7 @@ export class OdspDriverUrlResolverForShareLink implements IUrlResolver {
|
|
|
237
248
|
|
|
238
249
|
/**
|
|
239
250
|
* Crafts a supported data store nav param
|
|
251
|
+
* @deprecated encodeOdspFluidDataStoreLocator should be used instead
|
|
240
252
|
*/
|
|
241
253
|
public static createNavParam(locator: OdspFluidDataStoreLocator) {
|
|
242
254
|
return encodeOdspFluidDataStoreLocator(locator);
|
package/src/odspFluidFileLink.ts
CHANGED
|
@@ -9,13 +9,14 @@ import { OdcFileSiteOrigin, OdcApiSiteOrigin } from "./constants";
|
|
|
9
9
|
|
|
10
10
|
const fluidSignature = "1";
|
|
11
11
|
const fluidSignatureParamName = "fluid";
|
|
12
|
-
const
|
|
13
|
-
const
|
|
14
|
-
const
|
|
15
|
-
const
|
|
16
|
-
const
|
|
17
|
-
const
|
|
18
|
-
const
|
|
12
|
+
const sitePathParamName = "s";
|
|
13
|
+
const driveIdParamName = "d";
|
|
14
|
+
const itemIdParamName = "f";
|
|
15
|
+
const dataStorePathParamName = "c";
|
|
16
|
+
const appNameParamName = "a";
|
|
17
|
+
const containerPackageNameParamName = "p";
|
|
18
|
+
const fileVersionParamName = "v";
|
|
19
|
+
const additionalContextParamName = "x";
|
|
19
20
|
|
|
20
21
|
/**
|
|
21
22
|
* Transforms given Fluid data store locator into string that can be embedded into url
|
|
@@ -29,19 +30,20 @@ export function encodeOdspFluidDataStoreLocator(locator: OdspFluidDataStoreLocat
|
|
|
29
30
|
const itemId = encodeURIComponent(locator.itemId);
|
|
30
31
|
const dataStorePath = encodeURIComponent(locator.dataStorePath);
|
|
31
32
|
|
|
32
|
-
let locatorSerialized = `${
|
|
33
|
-
|
|
33
|
+
let locatorSerialized = `${sitePathParamName}=${sitePath}&${driveIdParamName}=${driveId}&${
|
|
34
|
+
itemIdParamName}=${itemId}&${dataStorePathParamName}=${dataStorePath}&${
|
|
34
35
|
fluidSignatureParamName}=${fluidSignature}`;
|
|
35
36
|
if (locator.appName) {
|
|
36
|
-
locatorSerialized += `&${
|
|
37
|
+
locatorSerialized += `&${appNameParamName}=${encodeURIComponent(locator.appName)}`;
|
|
37
38
|
}
|
|
38
39
|
if (locator.containerPackageName) {
|
|
39
|
-
locatorSerialized += `&${
|
|
40
|
-
encodeURIComponent(locator.containerPackageName)}`;
|
|
40
|
+
locatorSerialized += `&${containerPackageNameParamName}=${encodeURIComponent(locator.containerPackageName)}`;
|
|
41
41
|
}
|
|
42
42
|
if (locator.fileVersion) {
|
|
43
|
-
locatorSerialized += `&${
|
|
44
|
-
|
|
43
|
+
locatorSerialized += `&${fileVersionParamName}=${encodeURIComponent(locator.fileVersion)}`;
|
|
44
|
+
}
|
|
45
|
+
if (locator.context) {
|
|
46
|
+
locatorSerialized += `&${additionalContextParamName}=${encodeURIComponent(locator.context)}`;
|
|
45
47
|
}
|
|
46
48
|
|
|
47
49
|
return fromUtf8ToBase64(locatorSerialized);
|
|
@@ -65,15 +67,16 @@ function decodeOdspFluidDataStoreLocator(
|
|
|
65
67
|
return undefined;
|
|
66
68
|
}
|
|
67
69
|
|
|
68
|
-
const sitePath = locatorInfo.get(
|
|
69
|
-
const driveId = locatorInfo.get(
|
|
70
|
-
const itemId = locatorInfo.get(
|
|
71
|
-
const dataStorePath = locatorInfo.get(
|
|
72
|
-
const appName = locatorInfo.get(
|
|
73
|
-
const containerPackageName = locatorInfo.get(
|
|
74
|
-
const fileVersion = locatorInfo.get(
|
|
70
|
+
const sitePath = locatorInfo.get(sitePathParamName);
|
|
71
|
+
const driveId = locatorInfo.get(driveIdParamName);
|
|
72
|
+
const itemId = locatorInfo.get(itemIdParamName);
|
|
73
|
+
const dataStorePath = locatorInfo.get(dataStorePathParamName);
|
|
74
|
+
const appName = locatorInfo.get(appNameParamName) ?? undefined;
|
|
75
|
+
const containerPackageName = locatorInfo.get(containerPackageNameParamName) ?? undefined;
|
|
76
|
+
const fileVersion = locatorInfo.get(fileVersionParamName) ?? undefined;
|
|
77
|
+
const context = locatorInfo.get(additionalContextParamName) ?? undefined;
|
|
75
78
|
// "" is a valid value for dataStorePath so simply check for absence of the param;
|
|
76
|
-
//
|
|
79
|
+
// file storage locator params must be present and non-empty
|
|
77
80
|
if (!sitePath || !driveId || !itemId || dataStorePath === null) {
|
|
78
81
|
return undefined;
|
|
79
82
|
}
|
|
@@ -97,6 +100,7 @@ function decodeOdspFluidDataStoreLocator(
|
|
|
97
100
|
appName,
|
|
98
101
|
containerPackageName,
|
|
99
102
|
fileVersion,
|
|
103
|
+
context,
|
|
100
104
|
};
|
|
101
105
|
}
|
|
102
106
|
|
package/src/packageVersion.ts
CHANGED