@fluid-tools/fetch-tool 0.53.0-46105

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.
Files changed (41) hide show
  1. package/.eslintrc.js +17 -0
  2. package/LICENSE +21 -0
  3. package/README.md +114 -0
  4. package/bin/fluid-fetch +2 -0
  5. package/dist/fluidAnalyzeMessages.d.ts +8 -0
  6. package/dist/fluidAnalyzeMessages.d.ts.map +1 -0
  7. package/dist/fluidAnalyzeMessages.js +598 -0
  8. package/dist/fluidAnalyzeMessages.js.map +1 -0
  9. package/dist/fluidFetch.d.ts +6 -0
  10. package/dist/fluidFetch.d.ts.map +1 -0
  11. package/dist/fluidFetch.js +119 -0
  12. package/dist/fluidFetch.js.map +1 -0
  13. package/dist/fluidFetchArgs.d.ts +35 -0
  14. package/dist/fluidFetchArgs.d.ts.map +1 -0
  15. package/dist/fluidFetchArgs.js +206 -0
  16. package/dist/fluidFetchArgs.js.map +1 -0
  17. package/dist/fluidFetchInit.d.ts +9 -0
  18. package/dist/fluidFetchInit.d.ts.map +1 -0
  19. package/dist/fluidFetchInit.js +161 -0
  20. package/dist/fluidFetchInit.js.map +1 -0
  21. package/dist/fluidFetchMessages.d.ts +7 -0
  22. package/dist/fluidFetchMessages.d.ts.map +1 -0
  23. package/dist/fluidFetchMessages.js +264 -0
  24. package/dist/fluidFetchMessages.js.map +1 -0
  25. package/dist/fluidFetchSharePoint.d.ts +10 -0
  26. package/dist/fluidFetchSharePoint.d.ts.map +1 -0
  27. package/dist/fluidFetchSharePoint.js +95 -0
  28. package/dist/fluidFetchSharePoint.js.map +1 -0
  29. package/dist/fluidFetchSnapshot.d.ts +7 -0
  30. package/dist/fluidFetchSnapshot.d.ts.map +1 -0
  31. package/dist/fluidFetchSnapshot.js +289 -0
  32. package/dist/fluidFetchSnapshot.js.map +1 -0
  33. package/package.json +65 -0
  34. package/src/fluidAnalyzeMessages.ts +687 -0
  35. package/src/fluidFetch.ts +123 -0
  36. package/src/fluidFetchArgs.ts +224 -0
  37. package/src/fluidFetchInit.ts +168 -0
  38. package/src/fluidFetchMessages.ts +280 -0
  39. package/src/fluidFetchSharePoint.ts +141 -0
  40. package/src/fluidFetchSnapshot.ts +383 -0
  41. package/tsconfig.json +17 -0
@@ -0,0 +1,123 @@
1
+ /*!
2
+ * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
3
+ * Licensed under the MIT License.
4
+ */
5
+
6
+ import fs from "fs";
7
+ import util from "util";
8
+ import { isOdspHostname, IOdspDriveItem } from "@fluidframework/odsp-doclib-utils";
9
+ import { paramSaveDir, paramURL, parseArguments } from "./fluidFetchArgs";
10
+ import { connectionInfo, fluidFetchInit } from "./fluidFetchInit";
11
+ import { fluidFetchMessages } from "./fluidFetchMessages";
12
+ import { getSharepointFiles, getSingleSharePointFile } from "./fluidFetchSharePoint";
13
+ import { fluidFetchSnapshot } from "./fluidFetchSnapshot";
14
+
15
+ async function fluidFetchOneFile(urlStr: string, name?: string) {
16
+ const documentService = await fluidFetchInit(urlStr);
17
+ const saveDir = paramSaveDir ? (name ? `${paramSaveDir}/${name}` : paramSaveDir) : undefined;
18
+ if (saveDir !== undefined) {
19
+ const mkdir = util.promisify(fs.mkdir);
20
+ const writeFile = util.promisify(fs.writeFile);
21
+ await mkdir(saveDir, { recursive: true });
22
+ const info = {
23
+ creationDate: new Date().toString(),
24
+ connectionInfo,
25
+ url: urlStr,
26
+ };
27
+ await writeFile(`${saveDir}/info.json`, JSON.stringify(info, undefined, 2));
28
+ }
29
+
30
+ await fluidFetchSnapshot(documentService, saveDir);
31
+ await fluidFetchMessages(documentService, saveDir);
32
+ }
33
+
34
+ async function tryFluidFetchOneSharePointFile(server: string, driveItem: IOdspDriveItem) {
35
+ const { path, name, driveId, itemId } = driveItem;
36
+ console.log(`File: ${path}/${name}`);
37
+ await fluidFetchOneFile(`https://${server}/_api/v2.1/drives/${driveId}/items/${itemId}`, name);
38
+ }
39
+
40
+ function getSharePointSpecificDriveItem(url: URL): { driveId: string; itemId: string } | undefined {
41
+ if (url.searchParams.has("driveId") && url.searchParams.has("itemId")) {
42
+ return {
43
+ driveId: url.searchParams.get("driveId") as string,
44
+ itemId: url.searchParams.get("itemId") as string,
45
+ };
46
+ }
47
+ }
48
+
49
+ function getSharepointServerRelativePathFromURL(url: URL) {
50
+ if (url.pathname.startsWith("/_api/v2.1/drives/")) {
51
+ return undefined;
52
+ }
53
+
54
+ const hostnameParts = url.hostname.split(".");
55
+ const suffix = hostnameParts[0].endsWith("-my") ? "/_layouts/15/onedrive.aspx" : "/forms/allitems.aspx";
56
+
57
+ let sitePath = url.pathname;
58
+ if (url.searchParams.has("id")) {
59
+ sitePath = url.searchParams.get("id") as string;
60
+ } else if (url.searchParams.has("RootFolder")) {
61
+ sitePath = url.searchParams.get("RootFolder") as string;
62
+ } else if (url.pathname.toLowerCase().endsWith(suffix)) {
63
+ sitePath = sitePath.substr(0, url.pathname.length - suffix.length);
64
+ }
65
+
66
+ return decodeURI(sitePath);
67
+ }
68
+
69
+ async function fluidFetchMain() {
70
+ if (!paramURL) {
71
+ return;
72
+ }
73
+
74
+ const url = new URL(paramURL);
75
+ const server = url.hostname;
76
+ if (isOdspHostname(server)) {
77
+ // See if the url already has the specific item
78
+ const driveItem = getSharePointSpecificDriveItem(url);
79
+ if (driveItem) {
80
+ const file = await getSingleSharePointFile(server, driveItem.driveId, driveItem.itemId);
81
+ await tryFluidFetchOneSharePointFile(server, file);
82
+ return;
83
+ }
84
+
85
+ // See if the url given represent a sharepoint directory
86
+ const serverRelativePath = getSharepointServerRelativePathFromURL(url);
87
+ if (serverRelativePath) {
88
+ const files = await getSharepointFiles(server, serverRelativePath, false);
89
+ for (const file of files) {
90
+ if (file.name.endsWith(".b") || file.name.endsWith(".fluid")) {
91
+ await tryFluidFetchOneSharePointFile(server, file);
92
+ }
93
+ }
94
+ return;
95
+ }
96
+ }
97
+
98
+ return fluidFetchOneFile(paramURL);
99
+ }
100
+
101
+ parseArguments();
102
+
103
+ // eslint-disable-next-line @typescript-eslint/no-floating-promises
104
+ fluidFetchMain()
105
+ .catch((error: Error) => {
106
+ if (error instanceof Error) {
107
+ let extraMsg = "";
108
+ for (const key of Object.keys(error)) {
109
+ // error[key] might have circular structure
110
+ try {
111
+ if (key !== "message" && key !== "stack") {
112
+ extraMsg += `\n${key}: ${JSON.stringify(error[key], undefined, 2)}`;
113
+ }
114
+ } catch (_) {}
115
+ }
116
+ console.error(`ERROR: ${error.stack}${extraMsg}`);
117
+ } else if (typeof error === "object") {
118
+ console.error(`ERROR: Unknown exception object\n${JSON.stringify(error, undefined, 2)}`);
119
+ } else {
120
+ console.error(`ERROR: ${error}`);
121
+ }
122
+ })
123
+ .then(() => process.exit(0));
@@ -0,0 +1,224 @@
1
+ /*!
2
+ * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
3
+ * Licensed under the MIT License.
4
+ */
5
+
6
+ import fs from "fs";
7
+ import { URL } from "url";
8
+
9
+ export let dumpMessages = false;
10
+ export let dumpMessageStats = false;
11
+ export let dumpSnapshotStats = false;
12
+ export let dumpSnapshotTrees = false;
13
+ export let dumpSnapshotVersions = false;
14
+ export let overWrite = false;
15
+ export let paramSnapshotVersionIndex: number | undefined;
16
+ export let paramNumSnapshotVersions = 10;
17
+ export let paramUnpackAggregatedBlobs = true;
18
+ export let paramActualFormatting = false;
19
+
20
+ let paramForceTokenReauth = false;
21
+
22
+ // Only return true once, to reauth on first call.
23
+ export function getForceTokenReauth() {
24
+ const result = paramForceTokenReauth;
25
+ paramForceTokenReauth = false;
26
+ return result;
27
+ }
28
+
29
+ export let paramSaveDir: string | undefined;
30
+ export const messageTypeFilter = new Set<string>();
31
+
32
+ export let paramURL: string | undefined;
33
+ export let paramJWT: string;
34
+ export let paramAzureKey: string;
35
+
36
+ export let connectToWebSocket = false;
37
+
38
+ export let localDataOnly = false;
39
+
40
+ export let paramSite: string | undefined;
41
+
42
+ const optionsArray =
43
+ [
44
+ ["--dump:rawmessage", "dump all messages"],
45
+ ["--dump:snapshotVersion", "dump a list of snapshot version"],
46
+ ["--dump:snapshotTree", "dump the snapshot trees"],
47
+ ["--forceTokenReauth", "Force reauthorize token (SPO only)"],
48
+ ["--stat:message", "show message type, channel type, data type statistics"],
49
+ ["--stat:snapshot", "show a table of snapshot path and blob size"],
50
+ ["--stat", "Show both messages & snapshot stats"],
51
+ ["--filter:messageType <type>", "filter message by <type>"],
52
+ ["--jwt <token>", "token to be used for routerlicious URLs"],
53
+ ["--azureKey <key>", "secret key for Azure Fluid Relay instance"],
54
+ ["--numSnapshotVersions <number>", "Number of versions to load (default:10)"],
55
+ ["--noUnpack", "Do not unpack aggregated blobs"],
56
+ ["--actualPayload", "Do not format json payloads nicely, preserve actual bytes / formatting in storage"],
57
+ ["--saveDir <outdir>", "Save data of the snapshots and messages"],
58
+ ["--snapshotVersionIndex <number>", "Index of the version to dump"],
59
+ ["--websocket", "Connect to web socket to download initial messages"],
60
+ ["--local", "Do not connect to storage, use earlier downloaded data. Requires --saveDir."],
61
+ ];
62
+
63
+ export function printUsage() {
64
+ console.log("Usage: fluid-fetch [options] URL");
65
+ console.log("URL: <ODSP URL>|<Routerlicious URL>");
66
+ console.log("Options:");
67
+ for (const i of optionsArray) {
68
+ console.log(` ${i[0].padEnd(32)}: ${i[1]}`);
69
+ }
70
+ }
71
+
72
+ // Can be used in unit test to pass in customized argument values
73
+ // More argument options can be added when needed
74
+ export function setArguments(values: {
75
+ saveDir: string,
76
+ paramURL: string
77
+ dumpMessages?: boolean,
78
+ dumpMessageStats?: boolean,
79
+ dumpSnapshotStats?: boolean,
80
+ dumpSnapshotTrees?: boolean,
81
+ overWrite?: boolean }) {
82
+ paramSaveDir = values.saveDir;
83
+ paramURL = values.paramURL;
84
+ dumpMessages = values.dumpMessages ?? dumpMessages;
85
+ dumpMessageStats = values.dumpMessageStats ?? dumpMessageStats;
86
+ dumpSnapshotStats = values.dumpSnapshotStats ?? dumpSnapshotStats;
87
+ dumpSnapshotTrees = values.dumpSnapshotTrees ?? dumpSnapshotTrees;
88
+ overWrite = values.overWrite ?? overWrite;
89
+ }
90
+
91
+ export function parseArguments() {
92
+ for (let i = 2; i < process.argv.length; i++) {
93
+ const arg = process.argv[i];
94
+ switch (arg) {
95
+ case "--dump:rawmessage":
96
+ dumpMessages = true;
97
+ break;
98
+ case "--dump:rawmessage:overwrite":
99
+ dumpMessages = true;
100
+ overWrite = true;
101
+ break;
102
+ case "--stat:message":
103
+ dumpMessageStats = true;
104
+ break;
105
+ case "--stat":
106
+ dumpMessageStats = true;
107
+ dumpSnapshotStats = true;
108
+ break;
109
+ case "--filter:messageType":
110
+ messageTypeFilter.add(parseStrArg(i++, "type name for messageType filter"));
111
+ break;
112
+ case "--stat:snapshot":
113
+ dumpSnapshotStats = true;
114
+ break;
115
+ case "--dump:snapshotVersion":
116
+ dumpSnapshotVersions = true;
117
+ break;
118
+ case "--dump:snapshotTree":
119
+ dumpSnapshotTrees = true;
120
+ break;
121
+ case "--help":
122
+ printUsage();
123
+ process.exit(0);
124
+ case "--jwt":
125
+ paramJWT = parseStrArg(i++, "jwt token");
126
+ break;
127
+ case "--azureKey":
128
+ paramAzureKey = parseStrArg(i++, "Azure Fluid Relay key");
129
+ break;
130
+ case "--forceTokenReauth":
131
+ paramForceTokenReauth = true;
132
+ break;
133
+ case "--snapshotVersionIndex":
134
+ paramSnapshotVersionIndex = parseIntArg(i++, "version index", true);
135
+ break;
136
+ case "--numSnapshotVersions":
137
+ paramNumSnapshotVersions = parseIntArg(i++, "number of versions", false);
138
+ break;
139
+ case "--noUnpack":
140
+ paramUnpackAggregatedBlobs = false;
141
+ break;
142
+ case "--actualPayload":
143
+ paramActualFormatting = true;
144
+ break;
145
+ case "--saveDir":
146
+ paramSaveDir = parseStrArg(i++, "save data path");
147
+ break;
148
+ case "--websocket":
149
+ connectToWebSocket = true;
150
+ break;
151
+ case "--local":
152
+ localDataOnly = true;
153
+ break;
154
+ default:
155
+ try {
156
+ const url = new URL(arg);
157
+ if (url.protocol === "https:") {
158
+ paramURL = arg;
159
+ break;
160
+ }
161
+ if (url.protocol === "http:" && url.hostname === "localhost") {
162
+ paramURL = arg;
163
+ break;
164
+ }
165
+ } catch (e) {
166
+ console.error(e);
167
+ }
168
+
169
+ console.error(`ERROR: Invalid argument ${arg}`);
170
+ printUsage();
171
+ process.exit(-1);
172
+ break;
173
+ }
174
+ }
175
+ checkArgs();
176
+ }
177
+
178
+ function parseStrArg(i: number, name: string) {
179
+ if (i + 1 >= process.argv.length) {
180
+ console.error(`ERROR: Missing ${name}`);
181
+ printUsage();
182
+ process.exit(-1);
183
+ }
184
+ return process.argv[i + 1];
185
+ }
186
+ function parseIntArg(i: number, name: string, allowZero: boolean) {
187
+ if (i + 1 >= process.argv.length) {
188
+ console.error(`ERROR: Missing ${name}`);
189
+ printUsage();
190
+ process.exit(-1);
191
+ }
192
+ const numStr = process.argv[i + 1];
193
+ const paramNumber = parseInt(numStr, 10);
194
+ if (isNaN(paramNumber) || (allowZero ? paramNumber < 0 : paramNumber <= 0)) {
195
+ console.error(`ERROR: Invalid ${name} ${numStr}`);
196
+ printUsage();
197
+ process.exit(-1);
198
+ }
199
+ return paramNumber;
200
+ }
201
+
202
+ function checkArgs() {
203
+ if (paramSnapshotVersionIndex !== undefined) {
204
+ paramNumSnapshotVersions = Math.max(paramSnapshotVersionIndex + 1, paramNumSnapshotVersions);
205
+ }
206
+
207
+ if (!paramURL) {
208
+ if (paramSaveDir) {
209
+ const file = `${paramSaveDir}/info.json`;
210
+ if (fs.existsSync(file)) {
211
+ const info = JSON.parse(fs.readFileSync(file, { encoding: "utf-8" }));
212
+ paramURL = info.url;
213
+ } else {
214
+ console.log(`Can't find file ${file}`);
215
+ }
216
+ }
217
+
218
+ if (!paramURL) {
219
+ console.error("ERROR: Missing URL");
220
+ printUsage();
221
+ process.exit(-1);
222
+ }
223
+ }
224
+ }
@@ -0,0 +1,168 @@
1
+ /*!
2
+ * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
3
+ * Licensed under the MIT License.
4
+ */
5
+
6
+ import { URL } from "url";
7
+ import child_process from "child_process";
8
+ import { AzureUrlResolver } from "@fluidframework/azure-client/dist/AzureUrlResolver";
9
+ import {
10
+ generateTestUser,
11
+ InsecureTokenProvider,
12
+ } from "@fluidframework/test-client-utils";
13
+ import { IFluidResolvedUrl, IResolvedUrl, IUrlResolver } from "@fluidframework/driver-definitions";
14
+ import { configurableUrlResolver } from "@fluidframework/driver-utils";
15
+ import { FluidAppOdspUrlResolver } from "@fluid-tools/fluidapp-odsp-urlresolver";
16
+ import { IClientConfig, IOdspAuthRequestInfo } from "@fluidframework/odsp-doclib-utils";
17
+ import * as odsp from "@fluidframework/odsp-driver";
18
+ import { IOdspResolvedUrl, OdspResourceTokenFetchOptions } from "@fluidframework/odsp-driver-definitions";
19
+ import { OdspUrlResolver } from "@fluidframework/odsp-urlresolver";
20
+ import * as r11s from "@fluidframework/routerlicious-driver";
21
+ import { RouterliciousUrlResolver } from "@fluidframework/routerlicious-urlresolver";
22
+ import { getMicrosoftConfiguration } from "@fluidframework/tool-utils";
23
+ import { localDataOnly, paramAzureKey, paramJWT } from "./fluidFetchArgs";
24
+ import { resolveWrapper } from "./fluidFetchSharePoint";
25
+
26
+ export let latestVersionsId: string = "";
27
+ export let connectionInfo: any;
28
+
29
+ export const fluidFetchWebNavigator = (url: string) => {
30
+ let message = "Please open browser and navigate to this URL:";
31
+ if (process.platform === "win32") {
32
+ child_process.exec(`start "fluid-fetch" /B "${url}"`);
33
+ message = "Opening browser to get authorization code. If that doesn't open, please go to this URL manually";
34
+ }
35
+ console.log(`${message}\n ${url}`);
36
+ };
37
+
38
+ async function initializeODSPCore(
39
+ odspResolvedUrl: IOdspResolvedUrl,
40
+ server: string,
41
+ clientConfig: IClientConfig,
42
+ ) {
43
+ const { driveId, itemId } = odspResolvedUrl;
44
+
45
+ connectionInfo = {
46
+ server,
47
+ drive: driveId,
48
+ item: itemId,
49
+ };
50
+
51
+ if (localDataOnly) {
52
+ return;
53
+ }
54
+
55
+ const docId = await odsp.getHashedDocumentId(driveId, itemId);
56
+
57
+ console.log(`Connecting to ODSP:
58
+ server: ${server}
59
+ drive: ${driveId}
60
+ item: ${itemId}
61
+ docId: ${docId}`);
62
+
63
+ const getStorageTokenStub = async (options: OdspResourceTokenFetchOptions) => {
64
+ return resolveWrapper(
65
+ async (authRequestInfo: IOdspAuthRequestInfo) => {
66
+ if ((options.refresh || !authRequestInfo.accessToken) && authRequestInfo.refreshTokenFn) {
67
+ return authRequestInfo.refreshTokenFn();
68
+ }
69
+ return authRequestInfo.accessToken;
70
+ },
71
+ server,
72
+ clientConfig,
73
+ undefined,
74
+ true,
75
+ );
76
+ };
77
+ // eslint-disable-next-line @typescript-eslint/promise-function-async
78
+ const getWebsocketTokenStub = (_options: OdspResourceTokenFetchOptions) => Promise.resolve("");
79
+ const odspDocumentServiceFactory = new odsp.OdspDocumentServiceFactory(
80
+ getStorageTokenStub,
81
+ getWebsocketTokenStub,
82
+ undefined,
83
+ {
84
+ opsBatchSize: 20000,
85
+ concurrentOpsBatches: 4,
86
+ });
87
+ return odspDocumentServiceFactory.createDocumentService(odspResolvedUrl);
88
+ }
89
+
90
+ async function initializeR11s(server: string, pathname: string, r11sResolvedUrl: IFluidResolvedUrl) {
91
+ const path = pathname.split("/");
92
+ let tenantId: string;
93
+ let documentId: string;
94
+ if (server === "localhost" && path.length < 4) {
95
+ tenantId = "fluid";
96
+ documentId = path[2];
97
+ } else {
98
+ tenantId = path[2];
99
+ documentId = path[3];
100
+ }
101
+
102
+ // Latest version id is the documentId for r11s
103
+ latestVersionsId = documentId;
104
+
105
+ connectionInfo = {
106
+ server,
107
+ tenantId,
108
+ id: documentId,
109
+ };
110
+
111
+ if (localDataOnly) {
112
+ return;
113
+ }
114
+
115
+ console.log(`Connecting to r11s: tenantId=${tenantId} id:${documentId}`);
116
+ const tokenProvider = new r11s.DefaultTokenProvider(paramJWT);
117
+ const r11sDocumentServiceFactory = new r11s.RouterliciousDocumentServiceFactory(tokenProvider);
118
+ return r11sDocumentServiceFactory.createDocumentService(r11sResolvedUrl);
119
+ }
120
+
121
+ async function initializeAzure(resolvedUrl: IFluidResolvedUrl, tenantId: string) {
122
+ connectionInfo = {
123
+ server: resolvedUrl.endpoints.ordererUrl,
124
+ tenantId,
125
+ id: resolvedUrl.id,
126
+ };
127
+ console.log(`Connecting to Azure Fluid Relay: tenantId=${tenantId} id:${resolvedUrl.id}`);
128
+ const user = generateTestUser();
129
+ const tokenProvider = new InsecureTokenProvider(paramAzureKey, user);
130
+ const r11sDocumentServiceFactory = new r11s.RouterliciousDocumentServiceFactory(tokenProvider);
131
+ return r11sDocumentServiceFactory.createDocumentService(resolvedUrl);
132
+ }
133
+
134
+ async function resolveUrl(url: string): Promise<IResolvedUrl | undefined> {
135
+ const resolversList: IUrlResolver[] = [
136
+ new OdspUrlResolver(),
137
+ new FluidAppOdspUrlResolver(),
138
+ // eslint-disable-next-line @typescript-eslint/promise-function-async
139
+ new RouterliciousUrlResolver(undefined, () => Promise.resolve(paramJWT), ""),
140
+ new AzureUrlResolver(),
141
+ ];
142
+ const resolved = await configurableUrlResolver(resolversList, { url });
143
+ return resolved;
144
+ }
145
+
146
+ export async function fluidFetchInit(urlStr: string) {
147
+ const resolvedUrl = await resolveUrl(urlStr) as IFluidResolvedUrl;
148
+ if (!resolvedUrl) {
149
+ return Promise.reject(new Error(`Unknown URL ${urlStr}`));
150
+ }
151
+ const protocol = new URL(resolvedUrl.url).protocol;
152
+ if (protocol === "fluid-odsp:") {
153
+ const odspResolvedUrl = resolvedUrl as IOdspResolvedUrl;
154
+ return initializeODSPCore(odspResolvedUrl, new URL(odspResolvedUrl.siteUrl).host, getMicrosoftConfiguration());
155
+ } else if (protocol === "fluid:") {
156
+ const url = new URL(urlStr);
157
+ const server = url.hostname.toLowerCase();
158
+ return initializeR11s(server, url.pathname, resolvedUrl);
159
+ } else if (resolvedUrl.url.includes("fluidrelay.azure.com")) {
160
+ const url = new URL(urlStr);
161
+ const tenantId = url.searchParams.get("tenantId");
162
+ if (tenantId === null) {
163
+ throw new Error("Azure URL did not contain tenantId");
164
+ }
165
+ return initializeAzure(resolvedUrl, tenantId);
166
+ }
167
+ return Promise.reject(new Error(`Unknown resolved protocol ${protocol}`));
168
+ }