@fluid-tools/fetch-tool 2.1.0-274160 → 2.1.0-276985
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/README.md +2 -2
- package/dist/fluidFetchArgs.d.ts +1 -1
- package/dist/fluidFetchArgs.d.ts.map +1 -1
- package/dist/fluidFetchArgs.js +5 -13
- package/dist/fluidFetchArgs.js.map +1 -1
- package/dist/fluidFetchInit.d.ts.map +1 -1
- package/dist/fluidFetchInit.js +2 -3
- package/dist/fluidFetchInit.js.map +1 -1
- package/dist/fluidFetchMessages.d.ts.map +1 -1
- package/dist/fluidFetchMessages.js +21 -10
- package/dist/fluidFetchMessages.js.map +1 -1
- package/dist/fluidFetchSharePoint.d.ts +2 -1
- package/dist/fluidFetchSharePoint.d.ts.map +1 -1
- package/dist/fluidFetchSharePoint.js +48 -41
- package/dist/fluidFetchSharePoint.js.map +1 -1
- package/lib/fluidFetchArgs.d.ts +1 -1
- package/lib/fluidFetchArgs.d.ts.map +1 -1
- package/lib/fluidFetchArgs.js +5 -11
- package/lib/fluidFetchArgs.js.map +1 -1
- package/lib/fluidFetchInit.d.ts.map +1 -1
- package/lib/fluidFetchInit.js +3 -4
- package/lib/fluidFetchInit.js.map +1 -1
- package/lib/fluidFetchMessages.d.ts.map +1 -1
- package/lib/fluidFetchMessages.js +21 -10
- package/lib/fluidFetchMessages.js.map +1 -1
- package/lib/fluidFetchSharePoint.d.ts +2 -1
- package/lib/fluidFetchSharePoint.d.ts.map +1 -1
- package/lib/fluidFetchSharePoint.js +49 -39
- package/lib/fluidFetchSharePoint.js.map +1 -1
- package/package.json +17 -15
- package/src/fluidFetchArgs.ts +5 -13
- package/src/fluidFetchInit.ts +2 -5
- package/src/fluidFetchMessages.ts +27 -11
- package/src/fluidFetchSharePoint.ts +55 -55
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fluid-tools/fetch-tool",
|
|
3
|
-
"version": "2.1.0-
|
|
3
|
+
"version": "2.1.0-276985",
|
|
4
4
|
"description": "Console tool to fetch Fluid data from relay service",
|
|
5
5
|
"homepage": "https://fluidframework.com",
|
|
6
6
|
"repository": {
|
|
@@ -15,20 +15,22 @@
|
|
|
15
15
|
"fluid-fetch": "bin/fluid-fetch"
|
|
16
16
|
},
|
|
17
17
|
"dependencies": {
|
|
18
|
-
"@
|
|
19
|
-
"@
|
|
20
|
-
"@
|
|
21
|
-
"@fluidframework/
|
|
22
|
-
"@fluidframework/
|
|
23
|
-
"@fluidframework/
|
|
24
|
-
"@fluidframework/
|
|
25
|
-
"@fluidframework/
|
|
26
|
-
"@fluidframework/odsp-
|
|
27
|
-
"@fluidframework/odsp-
|
|
28
|
-
"@fluidframework/
|
|
29
|
-
"@fluidframework/
|
|
30
|
-
"@fluidframework/
|
|
31
|
-
"@fluidframework/
|
|
18
|
+
"@azure/identity": "^4.2.0",
|
|
19
|
+
"@azure/identity-cache-persistence": "^1.1.0",
|
|
20
|
+
"@fluid-internal/client-utils": "2.1.0-276985",
|
|
21
|
+
"@fluidframework/container-runtime": "2.1.0-276985",
|
|
22
|
+
"@fluidframework/core-interfaces": "2.1.0-276985",
|
|
23
|
+
"@fluidframework/core-utils": "2.1.0-276985",
|
|
24
|
+
"@fluidframework/datastore": "2.1.0-276985",
|
|
25
|
+
"@fluidframework/driver-definitions": "2.1.0-276985",
|
|
26
|
+
"@fluidframework/odsp-doclib-utils": "2.1.0-276985",
|
|
27
|
+
"@fluidframework/odsp-driver": "2.1.0-276985",
|
|
28
|
+
"@fluidframework/odsp-driver-definitions": "2.1.0-276985",
|
|
29
|
+
"@fluidframework/odsp-urlresolver": "2.1.0-276985",
|
|
30
|
+
"@fluidframework/routerlicious-driver": "2.1.0-276985",
|
|
31
|
+
"@fluidframework/routerlicious-urlresolver": "2.1.0-276985",
|
|
32
|
+
"@fluidframework/runtime-definitions": "2.1.0-276985",
|
|
33
|
+
"@fluidframework/tool-utils": "2.1.0-276985"
|
|
32
34
|
},
|
|
33
35
|
"devDependencies": {
|
|
34
36
|
"@biomejs/biome": "^1.7.3",
|
package/src/fluidFetchArgs.ts
CHANGED
|
@@ -15,15 +15,6 @@ export let paramSnapshotVersionIndex: number | undefined;
|
|
|
15
15
|
export let paramNumSnapshotVersions = 10;
|
|
16
16
|
export let paramActualFormatting = false;
|
|
17
17
|
|
|
18
|
-
let paramForceTokenReauth = false;
|
|
19
|
-
|
|
20
|
-
// Only return true once, to reauth on first call.
|
|
21
|
-
export function getForceTokenReauth() {
|
|
22
|
-
const result = paramForceTokenReauth;
|
|
23
|
-
paramForceTokenReauth = false;
|
|
24
|
-
return result;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
18
|
export let paramSaveDir: string | undefined;
|
|
28
19
|
export const messageTypeFilter = new Set<string>();
|
|
29
20
|
|
|
@@ -33,12 +24,12 @@ export let paramJWT: string;
|
|
|
33
24
|
export let connectToWebSocket = false;
|
|
34
25
|
|
|
35
26
|
export let localDataOnly = false;
|
|
27
|
+
export let loginHint: string | undefined;
|
|
36
28
|
|
|
37
29
|
const optionsArray = [
|
|
38
30
|
["--dump:rawmessage", "dump all messages"],
|
|
39
31
|
["--dump:snapshotVersion", "dump a list of snapshot version"],
|
|
40
32
|
["--dump:snapshotTree", "dump the snapshot trees"],
|
|
41
|
-
["--forceTokenReauth", "Force reauthorize token (SPO only)"],
|
|
42
33
|
["--stat:message", "show message type, channel type, data type statistics"],
|
|
43
34
|
["--stat:snapshot", "show a table of snapshot path and blob size"],
|
|
44
35
|
["--stat", "Show both messages & snapshot stats"],
|
|
@@ -53,6 +44,7 @@ const optionsArray = [
|
|
|
53
44
|
["--snapshotVersionIndex <number>", "Index of the version to dump"],
|
|
54
45
|
["--websocket", "Connect to web socket to download initial messages"],
|
|
55
46
|
["--local", "Do not connect to storage, use earlier downloaded data. Requires --saveDir."],
|
|
47
|
+
["--loginHint", "login hint for the user with document access."],
|
|
56
48
|
];
|
|
57
49
|
|
|
58
50
|
function printUsage() {
|
|
@@ -120,9 +112,6 @@ export function parseArguments() {
|
|
|
120
112
|
case "--jwt":
|
|
121
113
|
paramJWT = parseStrArg(i++, "jwt token");
|
|
122
114
|
break;
|
|
123
|
-
case "--forceTokenReauth":
|
|
124
|
-
paramForceTokenReauth = true;
|
|
125
|
-
break;
|
|
126
115
|
case "--snapshotVersionIndex":
|
|
127
116
|
paramSnapshotVersionIndex = parseIntArg(i++, "version index", true);
|
|
128
117
|
break;
|
|
@@ -141,6 +130,9 @@ export function parseArguments() {
|
|
|
141
130
|
case "--local":
|
|
142
131
|
localDataOnly = true;
|
|
143
132
|
break;
|
|
133
|
+
case "--loginHint":
|
|
134
|
+
loginHint = parseStrArg(i++, "login hint");
|
|
135
|
+
break;
|
|
144
136
|
default:
|
|
145
137
|
try {
|
|
146
138
|
const url = new URL(arg);
|
package/src/fluidFetchInit.ts
CHANGED
|
@@ -20,10 +20,9 @@ import {
|
|
|
20
20
|
} from "@fluidframework/odsp-urlresolver/internal";
|
|
21
21
|
import * as r11s from "@fluidframework/routerlicious-driver/internal";
|
|
22
22
|
import { RouterliciousUrlResolver } from "@fluidframework/routerlicious-urlresolver/internal";
|
|
23
|
-
import { getMicrosoftConfiguration } from "@fluidframework/tool-utils/internal";
|
|
24
23
|
|
|
25
24
|
import { localDataOnly, paramJWT } from "./fluidFetchArgs.js";
|
|
26
|
-
import { resolveWrapper } from "./fluidFetchSharePoint.js";
|
|
25
|
+
import { resolveWrapper, fetchToolClientConfig } from "./fluidFetchSharePoint.js";
|
|
27
26
|
|
|
28
27
|
export let latestVersionsId: string = "";
|
|
29
28
|
export let connectionInfo: any;
|
|
@@ -66,8 +65,6 @@ async function initializeODSPCore(
|
|
|
66
65
|
},
|
|
67
66
|
server,
|
|
68
67
|
clientConfig,
|
|
69
|
-
undefined,
|
|
70
|
-
true,
|
|
71
68
|
);
|
|
72
69
|
};
|
|
73
70
|
// eslint-disable-next-line @typescript-eslint/promise-function-async
|
|
@@ -173,7 +170,7 @@ export async function fluidFetchInit(urlStr: string) {
|
|
|
173
170
|
return initializeODSPCore(
|
|
174
171
|
odspResolvedUrl,
|
|
175
172
|
new URL(odspResolvedUrl.siteUrl).host,
|
|
176
|
-
|
|
173
|
+
fetchToolClientConfig,
|
|
177
174
|
);
|
|
178
175
|
} else if (resolvedInfo.serviceType === "r11s") {
|
|
179
176
|
const url = new URL(urlStr);
|
|
@@ -96,28 +96,44 @@ async function* loadAllSequencedMessages(
|
|
|
96
96
|
let requests = 0;
|
|
97
97
|
let opsStorage = 0;
|
|
98
98
|
|
|
99
|
+
console.log("fetching cached messages");
|
|
99
100
|
// reading only 1 op to test if there is mismatch
|
|
100
101
|
const teststream = deltaStorage.fetchMessages(lastSeq + 1, lastSeq + 2);
|
|
101
102
|
|
|
102
|
-
let statusCode;
|
|
103
|
-
let innerMostErrorCode;
|
|
104
|
-
let response;
|
|
105
|
-
|
|
106
103
|
try {
|
|
107
104
|
await teststream.read();
|
|
108
105
|
} catch (error: any) {
|
|
109
|
-
statusCode = error.getTelemetryProperties().statusCode;
|
|
110
|
-
innerMostErrorCode = error.getTelemetryProperties().innerMostErrorCode;
|
|
111
|
-
// if there is gap between ops, catch the error and check it is the error we need
|
|
106
|
+
const statusCode = error.getTelemetryProperties().statusCode;
|
|
107
|
+
const innerMostErrorCode = error.getTelemetryProperties().innerMostErrorCode;
|
|
112
108
|
if (statusCode !== 410 || innerMostErrorCode !== "fluidDeltaDataNotAvailable") {
|
|
113
109
|
throw error;
|
|
114
110
|
}
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
111
|
+
|
|
112
|
+
// This indicates we tried to fetch ops from storage that have been deleted (because they are past some retention policy).
|
|
113
|
+
// In that case, the error message should indicate the first sequence number that is available.
|
|
114
|
+
// We make a best-effort attempt for the original query (fetch all ops) by starting from that sequence number.
|
|
115
|
+
const props = error.getTelemetryProperties();
|
|
116
|
+
const { responseMessage } = props;
|
|
117
|
+
const [_, seq] =
|
|
118
|
+
typeof responseMessage === "string"
|
|
119
|
+
? responseMessage.match(/GenesisSequenceNumber '(\d+)'/) ?? []
|
|
120
|
+
: [];
|
|
121
|
+
if (seq !== undefined) {
|
|
122
|
+
lastSeq = parseInt(seq, 10);
|
|
123
|
+
firstAvailableDelta = lastSeq + 1;
|
|
124
|
+
console.log(
|
|
125
|
+
`Not all ops are available (older ops may have been deleted from storage). Starting from sequenceNumber: ${firstAvailableDelta}.`,
|
|
126
|
+
);
|
|
127
|
+
} else {
|
|
128
|
+
console.log(props);
|
|
129
|
+
throw new Error(
|
|
130
|
+
`Unexpected structure for 410 error: ${error.message}. Further error properties were logged above. This indicates a problem with fetch-tool.`,
|
|
131
|
+
);
|
|
132
|
+
}
|
|
119
133
|
}
|
|
120
134
|
|
|
135
|
+
console.log("fetching remaining messages from delta storage");
|
|
136
|
+
|
|
121
137
|
// continue reading rest of the ops
|
|
122
138
|
const stream = deltaStorage.fetchMessages(
|
|
123
139
|
lastSeq + 1, // inclusive left
|
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
* Licensed under the MIT License.
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import
|
|
7
|
-
|
|
6
|
+
import { InteractiveBrowserCredential, useIdentityPlugin } from "@azure/identity";
|
|
7
|
+
import { cachePersistencePlugin } from "@azure/identity-cache-persistence";
|
|
8
8
|
import { DriverErrorTypes } from "@fluidframework/driver-definitions/internal";
|
|
9
9
|
import {
|
|
10
10
|
IPublicClientConfig,
|
|
@@ -13,57 +13,71 @@ import {
|
|
|
13
13
|
getChildrenByDriveItem,
|
|
14
14
|
getDriveItemByServerRelativePath,
|
|
15
15
|
getDriveItemFromDriveAndItem,
|
|
16
|
-
|
|
16
|
+
getAadTenant,
|
|
17
|
+
getOdspScope,
|
|
17
18
|
} from "@fluidframework/odsp-doclib-utils/internal";
|
|
18
|
-
import {
|
|
19
|
-
IOdspTokenManagerCacheKey,
|
|
20
|
-
OdspTokenConfig,
|
|
21
|
-
OdspTokenManager,
|
|
22
|
-
getMicrosoftConfiguration,
|
|
23
|
-
odspTokensCache,
|
|
24
|
-
} from "@fluidframework/tool-utils/internal";
|
|
25
19
|
|
|
26
|
-
import {
|
|
20
|
+
import { loginHint } from "./fluidFetchArgs.js";
|
|
21
|
+
|
|
22
|
+
// Note: the following page may be helpful for debugging auth issues:
|
|
23
|
+
// https://github.com/Azure/azure-sdk-for-js/blob/main/sdk/identity/identity/TROUBLESHOOTING.md
|
|
24
|
+
// See e.g. the section on setting 'AZURE_LOG_LEVEL'.
|
|
25
|
+
useIdentityPlugin(cachePersistencePlugin);
|
|
26
|
+
|
|
27
|
+
export const fetchToolClientConfig: IPublicClientConfig = {
|
|
28
|
+
get clientId(): string {
|
|
29
|
+
const clientId = process.env.fetch__tool__clientId;
|
|
30
|
+
if (clientId === undefined) {
|
|
31
|
+
throw new Error(
|
|
32
|
+
"Client ID environment variable not set: fetch__tool__clientId. Use the getkeys tool to populate it.",
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
return clientId;
|
|
36
|
+
},
|
|
37
|
+
};
|
|
27
38
|
|
|
28
39
|
export async function resolveWrapper<T>(
|
|
29
40
|
callback: (authRequestInfo: IOdspAuthRequestInfo) => Promise<T>,
|
|
30
41
|
server: string,
|
|
31
42
|
clientConfig: IPublicClientConfig,
|
|
32
43
|
forceTokenReauth = false,
|
|
33
|
-
forToken = false,
|
|
34
44
|
): Promise<T> {
|
|
35
45
|
try {
|
|
36
|
-
const
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
46
|
+
const credential = new InteractiveBrowserCredential({
|
|
47
|
+
clientId: fetchToolClientConfig.clientId,
|
|
48
|
+
tenantId: getAadTenant(server),
|
|
49
|
+
// NOTE: fetch-tool flows using multiple sets of user credentials haven't been well-tested.
|
|
50
|
+
// Some of the @azure/identity docs suggest we may need to manage authentication records and choose
|
|
51
|
+
// which one to use explicitly here if we have such scenarios.
|
|
52
|
+
// If we start doing this, it may be worth considering using disableAutomaticAuthentication here so we
|
|
53
|
+
// have better control over when interactive auth may be triggered.
|
|
54
|
+
// For now, fetch-tool doesn't work against personal accounts anyway so the only flow that might necessitate this
|
|
55
|
+
// would be grabbing documents using several identities (e.g. test accounts we use for stress testing).
|
|
56
|
+
// In that case, a simple workaround is to delete the cache that @azure/identity uses before running the tool.
|
|
57
|
+
// See docs on `tokenCachePersistenceOptions.name` for information on where this cache is stored.
|
|
58
|
+
loginHint,
|
|
59
|
+
tokenCachePersistenceOptions: {
|
|
60
|
+
enabled: true,
|
|
61
|
+
name: "fetch-tool",
|
|
62
|
+
},
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
const scope = getOdspScope(server);
|
|
66
|
+
|
|
67
|
+
const { token } = await credential.getToken(scope);
|
|
48
68
|
|
|
49
|
-
|
|
50
|
-
accessToken:
|
|
51
|
-
refreshTokenFn:
|
|
69
|
+
return await callback({
|
|
70
|
+
accessToken: token,
|
|
71
|
+
refreshTokenFn: async () => {
|
|
72
|
+
await credential.authenticate(scope);
|
|
73
|
+
const result = await credential.getToken(scope);
|
|
74
|
+
return result.token;
|
|
75
|
+
},
|
|
52
76
|
});
|
|
53
|
-
// If this is used for getting a token, then refresh the cache with new token.
|
|
54
|
-
if (forToken) {
|
|
55
|
-
const key: IOdspTokenManagerCacheKey = { isPush: false, userOrServer: server };
|
|
56
|
-
await odspTokenManager.updateTokensCache(key, {
|
|
57
|
-
accessToken: result as any as string,
|
|
58
|
-
refreshToken: tokens.refreshToken,
|
|
59
|
-
});
|
|
60
|
-
return result;
|
|
61
|
-
}
|
|
62
|
-
return result;
|
|
63
77
|
} catch (e: any) {
|
|
64
78
|
if (e.errorType === DriverErrorTypes.authorizationError && !forceTokenReauth) {
|
|
65
79
|
// Re-auth
|
|
66
|
-
return resolveWrapper<T>(callback, server, clientConfig, true
|
|
80
|
+
return resolveWrapper<T>(callback, server, clientConfig, true);
|
|
67
81
|
}
|
|
68
82
|
throw e;
|
|
69
83
|
}
|
|
@@ -101,12 +115,10 @@ export async function getSharepointFiles(
|
|
|
101
115
|
serverRelativePath: string,
|
|
102
116
|
recurse: boolean,
|
|
103
117
|
) {
|
|
104
|
-
const clientConfig = getMicrosoftConfiguration();
|
|
105
|
-
|
|
106
118
|
const fileInfo = await resolveDriveItemByServerRelativePath(
|
|
107
119
|
server,
|
|
108
120
|
serverRelativePath,
|
|
109
|
-
|
|
121
|
+
fetchToolClientConfig,
|
|
110
122
|
);
|
|
111
123
|
console.log(fileInfo);
|
|
112
124
|
const pendingFolder: { path: string; folder: IOdspDriveItem }[] = [];
|
|
@@ -124,7 +136,7 @@ export async function getSharepointFiles(
|
|
|
124
136
|
break;
|
|
125
137
|
}
|
|
126
138
|
const { path, folder } = folderInfo;
|
|
127
|
-
const children = await resolveChildrenByDriveItem(server, folder,
|
|
139
|
+
const children = await resolveChildrenByDriveItem(server, folder, fetchToolClientConfig);
|
|
128
140
|
for (const child of children) {
|
|
129
141
|
const childPath = `${path}/${child.name}`;
|
|
130
142
|
if (child.isFolder) {
|
|
@@ -140,22 +152,10 @@ export async function getSharepointFiles(
|
|
|
140
152
|
}
|
|
141
153
|
|
|
142
154
|
export async function getSingleSharePointFile(server: string, drive: string, item: string) {
|
|
143
|
-
const clientConfig = getMicrosoftConfiguration();
|
|
144
|
-
|
|
145
155
|
return resolveWrapper<IOdspDriveItem>(
|
|
146
156
|
// eslint-disable-next-line @typescript-eslint/promise-function-async
|
|
147
157
|
(authRequestInfo) => getDriveItemFromDriveAndItem(server, drive, item, authRequestInfo),
|
|
148
158
|
server,
|
|
149
|
-
|
|
159
|
+
fetchToolClientConfig,
|
|
150
160
|
);
|
|
151
161
|
}
|
|
152
|
-
|
|
153
|
-
const fluidFetchWebNavigator = (url: string) => {
|
|
154
|
-
let message = "Please open browser and navigate to this URL:";
|
|
155
|
-
if (process.platform === "win32") {
|
|
156
|
-
child_process.exec(`start "fluid-fetch" /B "${url}"`);
|
|
157
|
-
message =
|
|
158
|
-
"Opening browser to get authorization code. If that doesn't open, please go to this URL manually";
|
|
159
|
-
}
|
|
160
|
-
console.log(`${message}\n ${url}`);
|
|
161
|
-
};
|