@fluid-tools/fetch-tool 2.1.0-276326 → 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.
@@ -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
- // get firstAvailableDelta from the error response, and set current sequence number to that
116
- response = JSON.parse(error.getTelemetryProperties().response);
117
- firstAvailableDelta = response.error.firstAvailableDelta;
118
- lastSeq = firstAvailableDelta - 1;
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 child_process from "child_process";
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
- getOdspRefreshTokenFn,
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 { getForceTokenReauth } from "./fluidFetchArgs.js";
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 odspTokenManager = new OdspTokenManager(odspTokensCache);
48
- const tokenConfig: OdspTokenConfig = {
49
- type: "browserLogin",
50
- navigator: fluidFetchWebNavigator,
51
- };
52
- const tokens = await odspTokenManager.getOdspTokens(
53
- server,
54
- clientConfig,
55
- tokenConfig,
56
- undefined /* forceRefresh */,
57
- forceTokenReauth || getForceTokenReauth(),
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 result = await callback({
61
- accessToken: tokens.accessToken,
62
- refreshTokenFn: getOdspRefreshTokenFn(server, clientConfig, tokens),
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, forToken);
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
- };