@fluid-tools/fetch-tool 2.1.0-276326 → 2.1.0-281041
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 +1 -1
- 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 +1 -1
- package/dist/fluidFetchSharePoint.d.ts.map +1 -1
- package/dist/fluidFetchSharePoint.js +35 -35
- 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 +1 -1
- 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 +1 -1
- package/lib/fluidFetchSharePoint.d.ts.map +1 -1
- package/lib/fluidFetchSharePoint.js +37 -34
- package/lib/fluidFetchSharePoint.js.map +1 -1
- package/package.json +23 -21
- package/src/fluidFetchArgs.ts +5 -13
- package/src/fluidFetchInit.ts +0 -2
- package/src/fluidFetchMessages.ts +27 -11
- package/src/fluidFetchSharePoint.ts +40 -47
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
|
@@ -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,16 +13,16 @@ 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
|
-
odspTokensCache,
|
|
23
|
-
} from "@fluidframework/tool-utils/internal";
|
|
24
19
|
|
|
25
|
-
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
26
|
|
|
27
27
|
export const fetchToolClientConfig: IPublicClientConfig = {
|
|
28
28
|
get clientId(): string {
|
|
@@ -41,40 +41,43 @@ export async function resolveWrapper<T>(
|
|
|
41
41
|
server: string,
|
|
42
42
|
clientConfig: IPublicClientConfig,
|
|
43
43
|
forceTokenReauth = false,
|
|
44
|
-
forToken = false,
|
|
45
44
|
): Promise<T> {
|
|
46
45
|
try {
|
|
47
|
-
const
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
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);
|
|
59
66
|
|
|
60
|
-
const
|
|
61
|
-
|
|
62
|
-
|
|
67
|
+
const { token } = await credential.getToken(scope);
|
|
68
|
+
|
|
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
|
+
},
|
|
63
76
|
});
|
|
64
|
-
// If this is used for getting a token, then refresh the cache with new token.
|
|
65
|
-
if (forToken) {
|
|
66
|
-
const key: IOdspTokenManagerCacheKey = { isPush: false, userOrServer: server };
|
|
67
|
-
await odspTokenManager.updateTokensCache(key, {
|
|
68
|
-
accessToken: result as any as string,
|
|
69
|
-
refreshToken: tokens.refreshToken,
|
|
70
|
-
});
|
|
71
|
-
return result;
|
|
72
|
-
}
|
|
73
|
-
return result;
|
|
74
77
|
} catch (e: any) {
|
|
75
78
|
if (e.errorType === DriverErrorTypes.authorizationError && !forceTokenReauth) {
|
|
76
79
|
// Re-auth
|
|
77
|
-
return resolveWrapper<T>(callback, server, clientConfig, true
|
|
80
|
+
return resolveWrapper<T>(callback, server, clientConfig, true);
|
|
78
81
|
}
|
|
79
82
|
throw e;
|
|
80
83
|
}
|
|
@@ -156,13 +159,3 @@ export async function getSingleSharePointFile(server: string, drive: string, ite
|
|
|
156
159
|
fetchToolClientConfig,
|
|
157
160
|
);
|
|
158
161
|
}
|
|
159
|
-
|
|
160
|
-
const fluidFetchWebNavigator = (url: string) => {
|
|
161
|
-
let message = "Please open browser and navigate to this URL:";
|
|
162
|
-
if (process.platform === "win32") {
|
|
163
|
-
child_process.exec(`start "fluid-fetch" /B "${url}"`);
|
|
164
|
-
message =
|
|
165
|
-
"Opening browser to get authorization code. If that doesn't open, please go to this URL manually";
|
|
166
|
-
}
|
|
167
|
-
console.log(`${message}\n ${url}`);
|
|
168
|
-
};
|