@microsoft/power-apps-native-host 0.3.3 → 0.5.1
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 +60 -0
- package/compatibility/current.json +1 -1
- package/lib/NOTICE.txt +2957 -371
- package/lib/commonjs/auth/AuthContext.js +49 -22
- package/lib/commonjs/auth/targetAuthorization.js +53 -0
- package/lib/commonjs/bridge/NativePowerAppsBridge.js +33 -2
- package/lib/commonjs/bridge/PowerPagesBridge.js +1 -3
- package/lib/commonjs/bridge/setupNativeHost.js +6 -0
- package/lib/commonjs/config/expoConfig.js +14 -0
- package/lib/commonjs/config/runtimeTenantMode.js +16 -0
- package/lib/commonjs/config/runtimeTenantMode.web.js +9 -0
- package/lib/commonjs/config/tenantMode.js +68 -0
- package/lib/commonjs/connections/PowerPlatformConnectionApi.js +24 -27
- package/lib/commonjs/connections/connectionMetadata.js +2 -2
- package/lib/commonjs/connections/resolveConnections.js +8 -3
- package/lib/commonjs/connections/types.js +9 -2
- package/lib/commonjs/connections/useConnectionRefs.js +19 -10
- package/lib/commonjs/index.js +41 -2
- package/lib/commonjs/platform/CanvasAppResolution.js +33 -0
- package/lib/commonjs/platform/CanvasAppResolutionApi.js +69 -0
- package/lib/commonjs/platform/DataverseGlobalDiscoveryApi.js +50 -13
- package/lib/commonjs/platform/GlobalDiscovery.js +40 -1
- package/lib/commonjs/provider/PowerAppsHostProvider.js +47 -8
- package/lib/commonjs/provider/PowerAppsProvider.js +175 -9
- package/lib/commonjs/target/DefaultPowerAppsExperience.js +564 -0
- package/lib/commonjs/target/PowerAppsExperience.js +41 -0
- package/lib/commonjs/target/PowerAppsTargetBootstrap.js +534 -0
- package/lib/commonjs/target/errors.js +24 -0
- package/lib/commonjs/target/types.js +1 -0
- package/lib/commonjs/telemetry/index.js +2 -2
- package/lib/module/auth/AuthContext.js +49 -22
- package/lib/module/auth/targetAuthorization.js +48 -0
- package/lib/module/bridge/NativePowerAppsBridge.js +33 -2
- package/lib/module/bridge/PowerPagesBridge.js +1 -3
- package/lib/module/bridge/setupNativeHost.js +5 -0
- package/lib/module/config/expoConfig.js +14 -0
- package/lib/module/config/runtimeTenantMode.js +12 -0
- package/lib/module/config/runtimeTenantMode.web.js +5 -0
- package/lib/module/config/tenantMode.js +61 -0
- package/lib/module/connections/PowerPlatformConnectionApi.js +24 -27
- package/lib/module/connections/connectionMetadata.js +2 -2
- package/lib/module/connections/resolveConnections.js +9 -4
- package/lib/module/connections/types.js +9 -2
- package/lib/module/connections/useConnectionRefs.js +20 -11
- package/lib/module/index.js +5 -2
- package/lib/module/platform/CanvasAppResolution.js +27 -0
- package/lib/module/platform/CanvasAppResolutionApi.js +65 -0
- package/lib/module/platform/DataverseGlobalDiscoveryApi.js +49 -14
- package/lib/module/platform/GlobalDiscovery.js +37 -1
- package/lib/module/provider/PowerAppsHostProvider.js +47 -8
- package/lib/module/provider/PowerAppsProvider.js +179 -13
- package/lib/module/target/DefaultPowerAppsExperience.js +559 -0
- package/lib/module/target/PowerAppsExperience.js +34 -0
- package/lib/module/target/PowerAppsTargetBootstrap.js +529 -0
- package/lib/module/target/errors.js +19 -0
- package/lib/module/target/types.js +1 -0
- package/lib/module/telemetry/index.js +1 -1
- package/lib/typescript/commonjs/auth/AuthContext.d.ts +11 -2
- package/lib/typescript/commonjs/auth/targetAuthorization.d.ts +15 -0
- package/lib/typescript/commonjs/bridge/NativePowerAppsBridge.d.ts +6 -0
- package/lib/typescript/commonjs/bridge/setupNativeHost.d.ts +2 -0
- package/lib/typescript/commonjs/config/runtimeTenantMode.d.ts +3 -0
- package/lib/typescript/commonjs/config/runtimeTenantMode.web.d.ts +3 -0
- package/lib/typescript/commonjs/config/tenantMode.d.ts +14 -0
- package/lib/typescript/commonjs/connections/PowerPlatformConnectionApi.d.ts +5 -2
- package/lib/typescript/commonjs/connections/connectionMetadata.d.ts +1 -1
- package/lib/typescript/commonjs/connections/types.d.ts +7 -1
- package/lib/typescript/commonjs/connections/useConnectionRefs.d.ts +11 -2
- package/lib/typescript/commonjs/index.d.ts +7 -3
- package/lib/typescript/commonjs/platform/CanvasAppResolution.d.ts +4 -0
- package/lib/typescript/commonjs/platform/CanvasAppResolutionApi.d.ts +6 -0
- package/lib/typescript/commonjs/platform/DataverseGlobalDiscoveryApi.d.ts +12 -2
- package/lib/typescript/commonjs/platform/GlobalDiscovery.d.ts +12 -0
- package/lib/typescript/commonjs/provider/PowerAppsHostProvider.d.ts +9 -1
- package/lib/typescript/commonjs/provider/PowerAppsProvider.d.ts +14 -16
- package/lib/typescript/commonjs/target/DefaultPowerAppsExperience.d.ts +6 -0
- package/lib/typescript/commonjs/target/PowerAppsExperience.d.ts +61 -0
- package/lib/typescript/commonjs/target/PowerAppsTargetBootstrap.d.ts +15 -0
- package/lib/typescript/commonjs/target/errors.d.ts +13 -0
- package/lib/typescript/commonjs/target/types.d.ts +34 -0
- package/lib/typescript/commonjs/telemetry/index.d.ts +2 -2
- package/lib/typescript/module/auth/AuthContext.d.ts +11 -2
- package/lib/typescript/module/auth/targetAuthorization.d.ts +15 -0
- package/lib/typescript/module/bridge/NativePowerAppsBridge.d.ts +6 -0
- package/lib/typescript/module/bridge/setupNativeHost.d.ts +2 -0
- package/lib/typescript/module/config/runtimeTenantMode.d.ts +3 -0
- package/lib/typescript/module/config/runtimeTenantMode.web.d.ts +3 -0
- package/lib/typescript/module/config/tenantMode.d.ts +14 -0
- package/lib/typescript/module/connections/PowerPlatformConnectionApi.d.ts +5 -2
- package/lib/typescript/module/connections/connectionMetadata.d.ts +1 -1
- package/lib/typescript/module/connections/types.d.ts +7 -1
- package/lib/typescript/module/connections/useConnectionRefs.d.ts +11 -2
- package/lib/typescript/module/index.d.ts +7 -3
- package/lib/typescript/module/platform/CanvasAppResolution.d.ts +4 -0
- package/lib/typescript/module/platform/CanvasAppResolutionApi.d.ts +6 -0
- package/lib/typescript/module/platform/DataverseGlobalDiscoveryApi.d.ts +12 -2
- package/lib/typescript/module/platform/GlobalDiscovery.d.ts +12 -0
- package/lib/typescript/module/provider/PowerAppsHostProvider.d.ts +9 -1
- package/lib/typescript/module/provider/PowerAppsProvider.d.ts +14 -16
- package/lib/typescript/module/target/DefaultPowerAppsExperience.d.ts +6 -0
- package/lib/typescript/module/target/PowerAppsExperience.d.ts +61 -0
- package/lib/typescript/module/target/PowerAppsTargetBootstrap.d.ts +15 -0
- package/lib/typescript/module/target/errors.d.ts +13 -0
- package/lib/typescript/module/target/types.d.ts +34 -0
- package/lib/typescript/module/telemetry/index.d.ts +2 -2
- package/package.json +12 -5
- package/scripts/build-codegen-package.js +34 -3
- package/scripts/dev.js +405 -0
- package/scripts/upgrade-authoring.js +85 -28
- package/scripts/wrap.js +42 -5
- package/src/config/generated-config.d.ts +21 -0
|
@@ -19,8 +19,10 @@ import { connectionIdsEqual, extractConnectionId } from "../connections/disambig
|
|
|
19
19
|
import { applyRegisteredConnectionUsages, getUnregisteredConnectionRequirements, isTransientConnectionConsentStatus } from "./connectionState.js";
|
|
20
20
|
import { getConnectionDatasetName } from "./types.js";
|
|
21
21
|
import { getAppLogger, HostOperationNames, platformHttpClient } from "../telemetry/index.js";
|
|
22
|
+
import { tryResolveTelemetryLocationFromEnvironment } from '@microsoft/power-apps-native-common';
|
|
22
23
|
import { getHttpCorrelationProperties } from '@microsoft/power-apps-native-common/http';
|
|
23
24
|
import { debugLog, debugWarn } from "../logger.js";
|
|
25
|
+
import { TargetRuntimeError } from "../target/errors.js";
|
|
24
26
|
|
|
25
27
|
// Power Platform connectivity API version used across all calls here.
|
|
26
28
|
const API_VERSION = '1';
|
|
@@ -121,8 +123,7 @@ export async function fetchConnectionsForConnector(connectorId, environmentId, r
|
|
|
121
123
|
}
|
|
122
124
|
});
|
|
123
125
|
if (!res.ok) {
|
|
124
|
-
|
|
125
|
-
throw new Error(`Unable to list connector connections (HTTP ${res.status}): ${errorText}`);
|
|
126
|
+
throw new Error(`Unable to list connector connections (HTTP ${res.status})`);
|
|
126
127
|
}
|
|
127
128
|
const body = await res.json();
|
|
128
129
|
return (body.value ?? []).map(c => {
|
|
@@ -143,7 +144,7 @@ export async function fetchConnectionsForConnector(connectorId, environmentId, r
|
|
|
143
144
|
});
|
|
144
145
|
} catch (error) {
|
|
145
146
|
if (error instanceof Error) throw error;
|
|
146
|
-
throw new Error(
|
|
147
|
+
throw new Error('Unable to list connector connections');
|
|
147
148
|
}
|
|
148
149
|
}
|
|
149
150
|
|
|
@@ -231,10 +232,9 @@ export async function createConnection(connectorId, environmentId, region, token
|
|
|
231
232
|
body: JSON.stringify(body)
|
|
232
233
|
});
|
|
233
234
|
if (!res.ok) {
|
|
234
|
-
const errText = await res.text().catch(() => res.statusText);
|
|
235
235
|
const result = {
|
|
236
236
|
status: 'error',
|
|
237
|
-
message: `HTTP ${res.status}
|
|
237
|
+
message: `Unable to create the connection (HTTP ${res.status})`
|
|
238
238
|
};
|
|
239
239
|
scenario.completeWithError({
|
|
240
240
|
httpStatus: res.status
|
|
@@ -267,14 +267,13 @@ export async function createConnection(connectorId, environmentId, region, token
|
|
|
267
267
|
body: JSON.stringify(buildConsentLinkDefinition(oauthRedirectUrl))
|
|
268
268
|
});
|
|
269
269
|
if (!consentRes.ok) {
|
|
270
|
-
const errorText = await consentRes.text().catch(() => consentRes.statusText);
|
|
271
270
|
scenario.completeWithError({
|
|
272
271
|
httpStatus: consentRes.status,
|
|
273
272
|
phase: 'getConsentLink'
|
|
274
273
|
});
|
|
275
274
|
return {
|
|
276
275
|
status: 'error',
|
|
277
|
-
message: `Unable to start connector consent (HTTP ${consentRes.status})
|
|
276
|
+
message: `Unable to start connector consent (HTTP ${consentRes.status})`
|
|
278
277
|
};
|
|
279
278
|
}
|
|
280
279
|
const consentData = await consentRes.json();
|
|
@@ -303,7 +302,7 @@ export async function createConnection(connectorId, environmentId, region, token
|
|
|
303
302
|
});
|
|
304
303
|
return {
|
|
305
304
|
status: 'error',
|
|
306
|
-
message:
|
|
305
|
+
message: 'Unable to create the connection.'
|
|
307
306
|
};
|
|
308
307
|
}
|
|
309
308
|
}
|
|
@@ -331,14 +330,13 @@ export async function repairConnection(connectorId, connectionName, environmentI
|
|
|
331
330
|
})
|
|
332
331
|
});
|
|
333
332
|
if (!updateResponse.ok) {
|
|
334
|
-
const errorText = await updateResponse.text().catch(() => updateResponse.statusText);
|
|
335
333
|
scenario.completeWithError({
|
|
336
334
|
httpStatus: updateResponse.status,
|
|
337
335
|
phase: 'update'
|
|
338
336
|
});
|
|
339
337
|
return {
|
|
340
338
|
status: 'error',
|
|
341
|
-
message: `Unable to update the connection (HTTP ${updateResponse.status})
|
|
339
|
+
message: `Unable to update the connection (HTTP ${updateResponse.status})`
|
|
342
340
|
};
|
|
343
341
|
}
|
|
344
342
|
if (!requiresApiConsent) {
|
|
@@ -357,7 +355,7 @@ export async function repairConnection(connectorId, connectionName, environmentI
|
|
|
357
355
|
});
|
|
358
356
|
return {
|
|
359
357
|
status: 'error',
|
|
360
|
-
message:
|
|
358
|
+
message: 'Unable to update the connection.'
|
|
361
359
|
};
|
|
362
360
|
}
|
|
363
361
|
}
|
|
@@ -382,14 +380,13 @@ export async function repairConnection(connectorId, connectionName, environmentI
|
|
|
382
380
|
body: JSON.stringify(buildConsentLinkDefinition(oauthRedirectUrl))
|
|
383
381
|
});
|
|
384
382
|
if (!response.ok) {
|
|
385
|
-
const errorText = await response.text().catch(() => response.statusText);
|
|
386
383
|
scenario.completeWithError({
|
|
387
384
|
httpStatus: response.status,
|
|
388
385
|
phase: 'getConsentLink'
|
|
389
386
|
});
|
|
390
387
|
return {
|
|
391
388
|
status: 'error',
|
|
392
|
-
message: `Unable to repair connector consent (HTTP ${response.status})
|
|
389
|
+
message: `Unable to repair connector consent (HTTP ${response.status})`
|
|
393
390
|
};
|
|
394
391
|
}
|
|
395
392
|
const data = await response.json();
|
|
@@ -418,7 +415,7 @@ export async function repairConnection(connectorId, connectionName, environmentI
|
|
|
418
415
|
});
|
|
419
416
|
return {
|
|
420
417
|
status: 'error',
|
|
421
|
-
message:
|
|
418
|
+
message: 'Unable to repair connector consent.'
|
|
422
419
|
};
|
|
423
420
|
}
|
|
424
421
|
}
|
|
@@ -588,8 +585,7 @@ export async function fetchAppConnectionUsages(appId, environmentId, region, tok
|
|
|
588
585
|
}
|
|
589
586
|
});
|
|
590
587
|
if (!response.ok) {
|
|
591
|
-
|
|
592
|
-
throw new Error(`Unable to load app connection usages (HTTP ${response.status}): ${errorText}`);
|
|
588
|
+
throw new Error(`Unable to load app connection usages (HTTP ${response.status})`);
|
|
593
589
|
}
|
|
594
590
|
const data = await response.json();
|
|
595
591
|
return (data.value ?? []).filter(usage => Boolean(usage.name && usage.properties?.reference && usage.properties?.apiId)).map(usage => ({
|
|
@@ -611,8 +607,7 @@ export async function postConnectionUsage(appId, usage, environmentId, region, t
|
|
|
611
607
|
body: JSON.stringify(buildConnectionUsageRequest(appId, usage))
|
|
612
608
|
});
|
|
613
609
|
if (!response.ok) {
|
|
614
|
-
|
|
615
|
-
throw new Error(`Unable to register connection usage (HTTP ${response.status}): ${errorText}`);
|
|
610
|
+
throw new Error(`Unable to register connection usage (HTTP ${response.status})`);
|
|
616
611
|
}
|
|
617
612
|
}
|
|
618
613
|
export async function reportScopeConsents(appId, usages, environmentId, region, token) {
|
|
@@ -627,8 +622,7 @@ export async function reportScopeConsents(appId, usages, environmentId, region,
|
|
|
627
622
|
body: JSON.stringify(buildScopeConsentRequest(usages))
|
|
628
623
|
});
|
|
629
624
|
if (!response.ok) {
|
|
630
|
-
|
|
631
|
-
throw new Error(`Unable to register connection scope consent (HTTP ${response.status}): ${errorText}`);
|
|
625
|
+
throw new Error(`Unable to register connection scope consent (HTTP ${response.status})`);
|
|
632
626
|
}
|
|
633
627
|
}
|
|
634
628
|
|
|
@@ -653,7 +647,7 @@ export async function reportScopeConsents(appId, usages, environmentId, region,
|
|
|
653
647
|
* @param environmentId - Environment GUID.
|
|
654
648
|
* @param region - Cluster region (default "prod").
|
|
655
649
|
* @param token - Bearer token for {@link powerPlatformScope}`(region)`.
|
|
656
|
-
* @returns Processed launch data
|
|
650
|
+
* @returns Processed launch data. Missing or inaccessible apps throw a typed error.
|
|
657
651
|
*/
|
|
658
652
|
export async function fetchAppLaunchData(appId, environmentId, region, token) {
|
|
659
653
|
const base = buildEnvironmentEndpoint(environmentId, region);
|
|
@@ -669,16 +663,17 @@ export async function fetchAppLaunchData(appId, environmentId, region, token) {
|
|
|
669
663
|
body: '{}'
|
|
670
664
|
});
|
|
671
665
|
} catch (error) {
|
|
672
|
-
throw new
|
|
666
|
+
throw new TargetRuntimeError('service-unavailable', 'launch', true, 'The app launch service is temporarily unavailable. Try again.', undefined, error);
|
|
673
667
|
}
|
|
674
668
|
if (!res.ok) {
|
|
675
669
|
const responseText = await res.text().catch(() => res.statusText);
|
|
676
670
|
if (isAppLaunchAccessDenied(res.status, responseText)) {
|
|
677
|
-
throw new
|
|
671
|
+
throw new TargetRuntimeError('app-access-denied', 'launch', false, 'You do not have access to this app. Ask the app owner or an administrator to share it with you.');
|
|
678
672
|
}
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
673
|
+
if (!getAppLaunchFailureMessage(res.status, res.statusText, responseText)) {
|
|
674
|
+
throw new TargetRuntimeError('app-not-found', 'launch', false, 'The configured app is missing from the selected environment.');
|
|
675
|
+
}
|
|
676
|
+
throw new TargetRuntimeError('service-unavailable', 'launch', res.status === 429 || res.status >= 500, res.status === 429 || res.status >= 500 ? 'The app launch service is temporarily unavailable. Try again.' : 'The app could not be loaded from the selected environment.');
|
|
682
677
|
}
|
|
683
678
|
try {
|
|
684
679
|
const data = await res.json();
|
|
@@ -843,9 +838,11 @@ export async function fetchAppLaunchData(appId, environmentId, region, token) {
|
|
|
843
838
|
}
|
|
844
839
|
}
|
|
845
840
|
const conflictingDataSources = findConnectionDataSourceConflicts(connectionRequirements);
|
|
841
|
+
const telemetryLocation = tryResolveTelemetryLocationFromEnvironment(data.app?.properties?.environment?.properties?.location, data.app?.properties?.environment?.properties?.cluster?.geoShortName);
|
|
846
842
|
const result = {
|
|
847
843
|
connectionRefs,
|
|
848
844
|
dataverseOrgUrl,
|
|
845
|
+
telemetryLocation,
|
|
849
846
|
databaseReferences: data.app?.properties?.databaseReferences ?? {},
|
|
850
847
|
missingConnectors,
|
|
851
848
|
missingConnectionReferences,
|
|
@@ -881,7 +878,7 @@ export async function fetchAppLaunchData(appId, environmentId, region, token) {
|
|
|
881
878
|
}));
|
|
882
879
|
return result;
|
|
883
880
|
} catch (error) {
|
|
884
|
-
throw new Error(
|
|
881
|
+
throw new Error('The Power Platform app launch response was invalid');
|
|
885
882
|
}
|
|
886
883
|
}
|
|
887
884
|
|
|
@@ -45,9 +45,9 @@ export function hasOAuthParameter(parameters) {
|
|
|
45
45
|
export function buildConnectionUserConsentUrl(environmentEndpoint, connectorId, connectionId) {
|
|
46
46
|
return `${environmentEndpoint}/connectivity/connectors/${encodeURIComponent(getConnectorId(connectorId))}` + `/connections/${encodeURIComponent(connectionId)}/userConsent?api-version=1`;
|
|
47
47
|
}
|
|
48
|
-
export function getAppLaunchFailureMessage(status,
|
|
48
|
+
export function getAppLaunchFailureMessage(status, _statusText, _responseText) {
|
|
49
49
|
if (status === 404) return null;
|
|
50
|
-
return `Unable to load the published app (HTTP ${status})
|
|
50
|
+
return `Unable to load the published app (HTTP ${status})`;
|
|
51
51
|
}
|
|
52
52
|
export function isAppLaunchAccessDenied(status, responseText) {
|
|
53
53
|
if (status !== 403) return false;
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
import { fetchConnectorMetadata, grantConnectionUserConsent } from "./PowerPlatformConnectionApi.js";
|
|
22
22
|
import { getConnectionNamesRequiringAclGrant, shouldGrantConnectionUserConsent } from "./connectionState.js";
|
|
23
23
|
import { connectionUserConsentScope, buildEnvironmentEndpoint } from "../platform/PowerPlatformEnvironmentApi.js";
|
|
24
|
-
import {
|
|
24
|
+
import { fetchInstanceMetadata } from "../platform/DataverseGlobalDiscoveryApi.js";
|
|
25
25
|
import { connectionIdsEqual, disambiguateConnections, extractConnectionId } from "../connections/disambiguate.js";
|
|
26
26
|
import { buildConnectionRequirements, ConnectionStatus, findConnectionDataSourceConflicts, getConnectionDatasetName, getFlowConnectionName, getFlowDataSourceNames, isCompositeConnectionApiId, getConnectorId, normalizeApiId } from "./types.js";
|
|
27
27
|
import { platformHttpClient } from "../telemetry/index.js";
|
|
@@ -95,8 +95,12 @@ export async function resolveViaStaticRefs({
|
|
|
95
95
|
Authorization: `Bearer ${token}`
|
|
96
96
|
};
|
|
97
97
|
|
|
98
|
-
//
|
|
99
|
-
|
|
98
|
+
// GDS provides authoritative geography when the environment has Dataverse,
|
|
99
|
+
// even if this app does not use it. Only Dataverse apps require the lookup.
|
|
100
|
+
const [instanceMetadata, ...connResults] = await Promise.all([fetchInstanceMetadata(environmentId, region, acquireToken, token).catch(error => {
|
|
101
|
+
if (hasDataverse) throw error;
|
|
102
|
+
return undefined;
|
|
103
|
+
}), ...Object.entries(directConnectionReferences).map(async ([localRefId, ref]) => {
|
|
100
104
|
const apiId = normalizeApiId(ref.id);
|
|
101
105
|
const schema = schemaByApiId[apiId];
|
|
102
106
|
if (!schema) {
|
|
@@ -252,7 +256,7 @@ export async function resolveViaStaticRefs({
|
|
|
252
256
|
unresolvedApiIds.push(normalizeApiId(dr.apiId));
|
|
253
257
|
}
|
|
254
258
|
}
|
|
255
|
-
const dataverseOrgUrl = (instanceUrl ?? '').replace(/\/$/, '');
|
|
259
|
+
const dataverseOrgUrl = (instanceMetadata?.instanceUrl ?? '').replace(/\/$/, '');
|
|
256
260
|
|
|
257
261
|
// Build the final AppConnectionReference map.
|
|
258
262
|
const connectionRefs = {};
|
|
@@ -295,6 +299,7 @@ export async function resolveViaStaticRefs({
|
|
|
295
299
|
return {
|
|
296
300
|
connectionRefs,
|
|
297
301
|
dataverseOrgUrl,
|
|
302
|
+
telemetryLocation: instanceMetadata?.telemetryLocation,
|
|
298
303
|
databaseReferences: {},
|
|
299
304
|
missingConnectors: [...new Set(unresolvedApiIds)],
|
|
300
305
|
missingConnectionReferences,
|
|
@@ -98,7 +98,7 @@ export function hasCompleteStaticConnectionConfig(connectionReferences, schemaMa
|
|
|
98
98
|
const directReferences = references.filter(reference => !isCompositeConnectionApiId(reference.id));
|
|
99
99
|
return directReferences.length > 0 && directReferences.every(reference => Boolean(reference.id) && reference.dataSources.length > 0 && reference.dataSources.every(dataSource => Boolean(schemaMap[dataSource]?.name && schemaMap[dataSource]?.properties?.primaryRuntimeUrl)));
|
|
100
100
|
}
|
|
101
|
-
export function buildConnectionResolutionKey(appId, environmentId, connectionReferences) {
|
|
101
|
+
export function buildConnectionResolutionKey(appId, environmentId, connectionReferences, targetIdentity) {
|
|
102
102
|
const references = Object.entries(connectionReferences).sort(([left], [right]) => left.localeCompare(right)).map(([localRefId, reference]) => ({
|
|
103
103
|
localRefId,
|
|
104
104
|
id: reference.id,
|
|
@@ -109,7 +109,14 @@ export function buildConnectionResolutionKey(appId, environmentId, connectionRef
|
|
|
109
109
|
return JSON.stringify({
|
|
110
110
|
appId: appId ?? '',
|
|
111
111
|
environmentId,
|
|
112
|
-
references
|
|
112
|
+
references,
|
|
113
|
+
...(targetIdentity ? {
|
|
114
|
+
tenantId: targetIdentity.tenantId?.toLowerCase() ?? '',
|
|
115
|
+
appName: targetIdentity.appName ?? '',
|
|
116
|
+
region: targetIdentity.region?.toLowerCase() ?? '',
|
|
117
|
+
generation: targetIdentity.generation ?? 0,
|
|
118
|
+
accountIdentifier: targetIdentity.accountIdentifier ?? ''
|
|
119
|
+
} : {})
|
|
113
120
|
});
|
|
114
121
|
}
|
|
115
122
|
|
|
@@ -36,7 +36,8 @@
|
|
|
36
36
|
import { useState, useEffect, useRef } from 'react';
|
|
37
37
|
import { isDevPlayer } from "../utils/isDevPlayer.js";
|
|
38
38
|
import { powerPlatformScope } from "../platform/PowerPlatformEnvironmentApi.js";
|
|
39
|
-
import {
|
|
39
|
+
import { fetchAppLaunchData } from "./PowerPlatformConnectionApi.js";
|
|
40
|
+
import { TargetRuntimeError } from "../target/errors.js";
|
|
40
41
|
import { applyAclGrants, resolveViaStaticRefs } from "./resolveConnections.js";
|
|
41
42
|
import { applyRegisteredConnectionUsages, getConnectionNamesRequiringAclGrant, invalidateFailedAclGrants, selectLaunchConnection } from "./connectionState.js";
|
|
42
43
|
import { debugLog, debugWarn } from "../logger.js";
|
|
@@ -68,7 +69,9 @@ export function useConnectionRefs({
|
|
|
68
69
|
schemaMap,
|
|
69
70
|
onDisambiguationNeeded,
|
|
70
71
|
onMissingConnection,
|
|
71
|
-
isvClientId
|
|
72
|
+
isvClientId,
|
|
73
|
+
onTelemetryLocationResolved,
|
|
74
|
+
targetIdentity
|
|
72
75
|
}) {
|
|
73
76
|
const [connectionRefs, setConnectionRefs] = useState({});
|
|
74
77
|
const [isReady, setIsReady] = useState(false);
|
|
@@ -91,7 +94,10 @@ export function useConnectionRefs({
|
|
|
91
94
|
const isBackgroundRetryRef = useRef(false);
|
|
92
95
|
const MAX_TOKEN_RETRIES = 5;
|
|
93
96
|
const TOKEN_RETRY_DELAY_MS = 2000;
|
|
94
|
-
const currentConfigKey = buildConnectionResolutionKey(appId, environmentId, isDevPlayer ? connectionReferences : {}
|
|
97
|
+
const currentConfigKey = buildConnectionResolutionKey(appId, environmentId, isDevPlayer ? connectionReferences : {}, targetIdentity ? {
|
|
98
|
+
...targetIdentity,
|
|
99
|
+
region
|
|
100
|
+
} : undefined);
|
|
95
101
|
useEffect(() => {
|
|
96
102
|
if (!isSignedIn) {
|
|
97
103
|
tokenRetryCountRef.current = 0;
|
|
@@ -200,9 +206,6 @@ export function useConnectionRefs({
|
|
|
200
206
|
let result = null;
|
|
201
207
|
if (appId && environmentId && !isDevPlayer) {
|
|
202
208
|
result = await fetchAppLaunchData(appId, environmentId, region, token);
|
|
203
|
-
if (!result) {
|
|
204
|
-
throw new Error(`App "${appId}" is not available in environment "${environmentId}". Push the mobile app to this environment and try again.`);
|
|
205
|
-
}
|
|
206
209
|
if (result && onDisambiguationNeeded) {
|
|
207
210
|
for (const requirement of [...result.missingConnectionReferences]) {
|
|
208
211
|
const candidates = result.connectionCandidates[requirement.localRefId] ?? [];
|
|
@@ -260,6 +263,9 @@ export function useConnectionRefs({
|
|
|
260
263
|
});
|
|
261
264
|
return;
|
|
262
265
|
}
|
|
266
|
+
if (result.telemetryLocation) {
|
|
267
|
+
onTelemetryLocationResolved?.(result.telemetryLocation);
|
|
268
|
+
}
|
|
263
269
|
|
|
264
270
|
// Pre-warm the Dataverse token while the user waits.
|
|
265
271
|
if (result.dataverseOrgUrl) {
|
|
@@ -296,15 +302,18 @@ export function useConnectionRefs({
|
|
|
296
302
|
errorName: e instanceof Error ? e.name : 'UnknownError'
|
|
297
303
|
});
|
|
298
304
|
getAppLogger().trackErrorEvent(HostOperationNames.PlatformLoadConnectionsFailed, {
|
|
299
|
-
|
|
305
|
+
errorName: e instanceof Error ? e.name : 'UnknownError',
|
|
306
|
+
errorCode: e instanceof TargetRuntimeError ? e.code : undefined,
|
|
300
307
|
hasAppId: Boolean(appId),
|
|
301
308
|
hasEnvironmentId: Boolean(environmentId),
|
|
302
309
|
connectionReferenceCount: Object.keys(connectionReferences).length,
|
|
303
310
|
runningInDevPlayer: isDevPlayer
|
|
304
311
|
});
|
|
305
|
-
const
|
|
306
|
-
|
|
307
|
-
|
|
312
|
+
const targetError = e instanceof TargetRuntimeError ? e : null;
|
|
313
|
+
const accessDenied = targetError?.code === 'app-access-denied';
|
|
314
|
+
const appNotFound = targetError?.code === 'app-not-found';
|
|
315
|
+
setErrorKind(accessDenied ? 'app-access-denied' : appNotFound ? 'app-not-found' : 'generic');
|
|
316
|
+
setError(targetError?.userMessage ?? (accessDenied ? 'You don’t have access to this app. Ask the app owner or an administrator to share it with you, then try again.' : 'Unable to load this app from Power Platform. Try again later.'));
|
|
308
317
|
setIsReady(true);
|
|
309
318
|
}
|
|
310
319
|
});
|
|
@@ -313,7 +322,7 @@ export function useConnectionRefs({
|
|
|
313
322
|
if (retryTimer) clearTimeout(retryTimer);
|
|
314
323
|
};
|
|
315
324
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
316
|
-
}, [isSignedIn, retryTick, appId, environmentId, region, connectionReferences, hasDataverse, schemaMap, isvClientId]);
|
|
325
|
+
}, [isSignedIn, retryTick, appId, environmentId, region, connectionReferences, hasDataverse, schemaMap, isvClientId, onTelemetryLocationResolved, targetIdentity?.tenantId, targetIdentity?.appName, targetIdentity?.generation, targetIdentity?.accountIdentifier]);
|
|
317
326
|
return {
|
|
318
327
|
connectionRefs,
|
|
319
328
|
isReady,
|
package/lib/module/index.js
CHANGED
|
@@ -13,14 +13,17 @@ import '@microsoft/power-apps-native-assets';
|
|
|
13
13
|
export { NativePowerAppsBridge } from "./bridge/NativePowerAppsBridge.js";
|
|
14
14
|
export { PowerPagesBridge, setupPowerPagesHost } from "./bridge/PowerPagesBridge.js";
|
|
15
15
|
export { buildConnectionReferences } from "./bridge/NativePowerAppsBridge.js";
|
|
16
|
-
export { setupNativeHost } from "./bridge/setupNativeHost.js";
|
|
16
|
+
export { invalidateNativeHostRuntime, setupNativeHost } from "./bridge/setupNativeHost.js";
|
|
17
17
|
export { updateBridgeOptionsInPlace } from "./bridge/bridgeState.js";
|
|
18
18
|
export { AuthProvider, useAuth } from './auth/AuthContext';
|
|
19
19
|
export { completePowerAppsAuthSession } from "./auth/completePowerAppsAuthSession.js";
|
|
20
20
|
export { PowerAppsHostProvider } from './provider/PowerAppsHostProvider';
|
|
21
21
|
export { PowerAppsProvider } from "./provider/PowerAppsProvider.js";
|
|
22
|
+
export { PowerAppsRuntimeGate, usePowerAppsExperience } from "./target/PowerAppsExperience.js";
|
|
22
23
|
export { buildEnvironmentEndpoint, POWER_PLATFORM_API_SCOPE } from "./platform/PowerPlatformEnvironmentApi.js";
|
|
23
24
|
export { fetchInstanceUrl } from "./platform/DataverseGlobalDiscoveryApi.js";
|
|
25
|
+
export { buildCanvasAppResolutionUrl, canonicalizeDataverseOrigin } from "./platform/CanvasAppResolution.js";
|
|
26
|
+
export { resolveCanvasApp } from "./platform/CanvasAppResolutionApi.js";
|
|
24
27
|
export { buildCdsDataSources } from "./platform/DataverseDataSources.js";
|
|
25
28
|
export { useConnectionRefs } from "./connections/useConnectionRefs.js";
|
|
26
29
|
// Connection creation & OAuth flow
|
|
@@ -48,6 +51,6 @@ export { isDevPlayer } from "./utils/isDevPlayer.js";
|
|
|
48
51
|
export { base64ToDataverseUploadFile, base64ToUint8Array, createDataverseUploadFile } from "./utils/dataverseUtils.js";
|
|
49
52
|
|
|
50
53
|
// ── Telemetry ─────────────────────────────────────────────────────────────────
|
|
51
|
-
export { getAppLogger, configureAppLogger,
|
|
54
|
+
export { getAppLogger, configureAppLogger, getCustomEventsLogger, HostOperationNames } from "./telemetry/index.js";
|
|
52
55
|
// ── Error boundary ────────────────────────────────────────────────────────────
|
|
53
56
|
export { ErrorBoundary } from "./ErrorBoundary.js";
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
export function canonicalizeDataverseOrigin(value) {
|
|
4
|
+
const url = new URL(value);
|
|
5
|
+
if (url.protocol !== 'https:' || url.username || url.password || url.search || url.hash || url.pathname !== '' && url.pathname !== '/') {
|
|
6
|
+
throw new Error('Dataverse URL must be an HTTPS origin');
|
|
7
|
+
}
|
|
8
|
+
return url.origin.toLowerCase();
|
|
9
|
+
}
|
|
10
|
+
export function areDataverseOriginAliases(left, right) {
|
|
11
|
+
const normalizeHost = value => {
|
|
12
|
+
const url = new URL(canonicalizeDataverseOrigin(value));
|
|
13
|
+
const labels = url.hostname.split('.');
|
|
14
|
+
if (labels[1] === 'api') labels.splice(1, 1);
|
|
15
|
+
return `${labels.join('.')}:${url.port}`;
|
|
16
|
+
};
|
|
17
|
+
return normalizeHost(left) === normalizeHost(right);
|
|
18
|
+
}
|
|
19
|
+
export function buildCanvasAppResolutionUrl(dataverseApiUrl, appName) {
|
|
20
|
+
const origin = canonicalizeDataverseOrigin(dataverseApiUrl);
|
|
21
|
+
const query = new URLSearchParams({
|
|
22
|
+
'$select': 'name,uniquecanvasappid,canvasapptype,componentstate',
|
|
23
|
+
'$filter': 'name eq @appName and componentstate eq 0',
|
|
24
|
+
'@appName': `'${appName.replace(/'/g, "''")}'`
|
|
25
|
+
});
|
|
26
|
+
return `${origin}/api/data/v9.2/canvasapps?${query.toString()}`;
|
|
27
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
import { getHttpCorrelationProperties } from '@microsoft/power-apps-native-common/http';
|
|
4
|
+
import { getAppLogger, HostOperationNames, platformHttpClient } from "../telemetry/index.js";
|
|
5
|
+
import { TargetRuntimeError } from "../target/errors.js";
|
|
6
|
+
import { buildCanvasAppResolutionUrl, canonicalizeDataverseOrigin } from "./CanvasAppResolution.js";
|
|
7
|
+
const GUID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
8
|
+
export async function resolveCanvasApp(target, dataverseApiUrl, acquireToken, generation) {
|
|
9
|
+
const assertCurrent = () => {
|
|
10
|
+
if (generation && !generation.isCurrent(generation.value)) {
|
|
11
|
+
throw new TargetRuntimeError('stale-operation', 'app-resolution', false, 'The selected environment changed before app resolution completed.', target.id);
|
|
12
|
+
}
|
|
13
|
+
};
|
|
14
|
+
const origin = canonicalizeDataverseOrigin(dataverseApiUrl);
|
|
15
|
+
const scenario = getAppLogger().trackScenario(HostOperationNames.PlatformFetchInstanceUrl, {
|
|
16
|
+
targetId: target.id,
|
|
17
|
+
generation: generation?.value
|
|
18
|
+
});
|
|
19
|
+
try {
|
|
20
|
+
const token = await acquireToken([`${origin}/user_impersonation`]);
|
|
21
|
+
assertCurrent();
|
|
22
|
+
if (!token) {
|
|
23
|
+
throw new TargetRuntimeError('app-metadata-access-denied', 'app-resolution', false, 'You do not have access to app metadata in the selected environment.', target.id);
|
|
24
|
+
}
|
|
25
|
+
const response = await platformHttpClient.request(buildCanvasAppResolutionUrl(origin, target.appName), {
|
|
26
|
+
headers: {
|
|
27
|
+
Authorization: `Bearer ${token}`,
|
|
28
|
+
Accept: 'application/json'
|
|
29
|
+
}
|
|
30
|
+
});
|
|
31
|
+
assertCurrent();
|
|
32
|
+
if (response.status === 401 || response.status === 403) {
|
|
33
|
+
throw new TargetRuntimeError('app-metadata-access-denied', 'app-resolution', false, 'You do not have access to app metadata in the selected environment.', target.id);
|
|
34
|
+
}
|
|
35
|
+
if (!response.ok) throw new Error(`Canvas App metadata request failed with status ${response.status}`);
|
|
36
|
+
const payload = await response.json();
|
|
37
|
+
assertCurrent();
|
|
38
|
+
const rows = payload.value ?? [];
|
|
39
|
+
if (rows.length === 0) {
|
|
40
|
+
throw new TargetRuntimeError('app-metadata-missing', 'app-resolution', false, 'The configured app is missing from the selected environment.', target.id);
|
|
41
|
+
}
|
|
42
|
+
if (rows.length !== 1) {
|
|
43
|
+
throw new TargetRuntimeError('app-metadata-ambiguous', 'app-resolution', false, 'More than one app matches the selected environment configuration.', target.id);
|
|
44
|
+
}
|
|
45
|
+
const row = rows[0];
|
|
46
|
+
const appId = row.uniquecanvasappid?.toLowerCase();
|
|
47
|
+
if (row.name !== target.appName || row.componentstate !== 0 || !appId || !GUID_PATTERN.test(appId)) {
|
|
48
|
+
throw new TargetRuntimeError('app-metadata-missing', 'app-resolution', false, 'The configured app metadata is invalid in the selected environment.', target.id);
|
|
49
|
+
}
|
|
50
|
+
scenario.complete({
|
|
51
|
+
...getHttpCorrelationProperties(response.headers)
|
|
52
|
+
});
|
|
53
|
+
return {
|
|
54
|
+
...target,
|
|
55
|
+
appId,
|
|
56
|
+
dataverseApiUrl: origin
|
|
57
|
+
};
|
|
58
|
+
} catch (error) {
|
|
59
|
+
scenario.fail(undefined, {
|
|
60
|
+
errorCode: error instanceof TargetRuntimeError ? error.code : 'service-unavailable'
|
|
61
|
+
});
|
|
62
|
+
if (error instanceof TargetRuntimeError) throw error;
|
|
63
|
+
throw new TargetRuntimeError('service-unavailable', 'app-resolution', true, 'App metadata is temporarily unavailable. Try again.', target.id, error);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
@@ -1,33 +1,38 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
|
|
3
|
+
import { tryResolveTelemetryLocationFromDataverseRegion } from '@microsoft/power-apps-native-common';
|
|
3
4
|
import { getHttpCorrelationProperties } from '@microsoft/power-apps-native-common/http';
|
|
4
5
|
import * as SecureStore from 'expo-secure-store';
|
|
5
6
|
import { getAppLogger, HostOperationNames, platformHttpClient } from "../telemetry/index.js";
|
|
6
|
-
import { buildGlobalDiscoveryUrl, globalDiscoveryScope } from "./GlobalDiscovery.js";
|
|
7
|
-
|
|
7
|
+
import { buildGlobalDiscoveryListUrl, buildGlobalDiscoveryUrl, globalDiscoveryScope, parseDiscoveredEnvironments } from "./GlobalDiscovery.js";
|
|
8
|
+
import { TargetRuntimeError } from "../target/errors.js";
|
|
9
|
+
const INSTANCE_URL_CACHE_KEY_PREFIX = 'microsoft.power-apps-native-host.instance-url.v3';
|
|
8
10
|
const INSTANCE_URL_CACHE_TTL_MS = 30 * 24 * 60 * 60 * 1000;
|
|
9
11
|
function getInstanceUrlCacheKey(environmentId, region) {
|
|
10
12
|
return `${INSTANCE_URL_CACHE_KEY_PREFIX}.${region.toLowerCase()}.${environmentId.toLowerCase()}`;
|
|
11
13
|
}
|
|
12
14
|
async function getCachedInstanceUrl(environmentId, region) {
|
|
13
15
|
try {
|
|
14
|
-
const
|
|
16
|
+
const cacheKey = getInstanceUrlCacheKey(environmentId, region);
|
|
17
|
+
const cachedValue = await SecureStore.getItemAsync(cacheKey);
|
|
15
18
|
if (!cachedValue) {
|
|
16
19
|
return null;
|
|
17
20
|
}
|
|
18
21
|
const entry = JSON.parse(cachedValue);
|
|
19
|
-
if (typeof entry.instanceUrl !== 'string' || typeof entry.expiresAt !== 'number' || entry.expiresAt <= Date.now()) {
|
|
22
|
+
if (typeof entry.instanceUrl !== 'string' || entry.telemetryLocation !== undefined && entry.telemetryLocation !== 'eu' && entry.telemetryLocation !== 'global' || typeof entry.expiresAt !== 'number' || entry.expiresAt <= Date.now()) {
|
|
23
|
+
await SecureStore.deleteItemAsync(cacheKey);
|
|
20
24
|
return null;
|
|
21
25
|
}
|
|
22
|
-
return entry
|
|
26
|
+
return entry;
|
|
23
27
|
} catch {
|
|
24
28
|
return null;
|
|
25
29
|
}
|
|
26
30
|
}
|
|
27
|
-
async function cacheInstanceUrl(environmentId, region, instanceUrl) {
|
|
31
|
+
async function cacheInstanceUrl(environmentId, region, instanceUrl, telemetryLocation) {
|
|
28
32
|
try {
|
|
29
33
|
const entry = {
|
|
30
34
|
instanceUrl,
|
|
35
|
+
telemetryLocation,
|
|
31
36
|
expiresAt: Date.now() + INSTANCE_URL_CACHE_TTL_MS
|
|
32
37
|
};
|
|
33
38
|
await SecureStore.setItemAsync(getInstanceUrlCacheKey(environmentId, region), JSON.stringify(entry));
|
|
@@ -39,20 +44,40 @@ function getSafeErrorName(error) {
|
|
|
39
44
|
return error instanceof Error ? error.constructor.name : typeof error;
|
|
40
45
|
}
|
|
41
46
|
|
|
42
|
-
/**
|
|
43
|
-
export async function
|
|
47
|
+
/** Enumerate enabled commercial-cloud Dataverse environments available to an authenticated tenant account. */
|
|
48
|
+
export async function fetchAccessibleEnvironments(tenantId, appDisplayName, appName, acquireToken) {
|
|
49
|
+
try {
|
|
50
|
+
const region = 'prod';
|
|
51
|
+
const token = await acquireToken([globalDiscoveryScope(region)]);
|
|
52
|
+
if (!token) throw new Error('Global Discovery token is unavailable');
|
|
53
|
+
const response = await platformHttpClient.request(buildGlobalDiscoveryListUrl(region), {
|
|
54
|
+
method: 'GET',
|
|
55
|
+
headers: {
|
|
56
|
+
Authorization: `Bearer ${token}`,
|
|
57
|
+
'Content-Type': 'application/json'
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
if (!response.ok) throw new Error(`Global Discovery returned ${response.status} ${response.statusText}`);
|
|
61
|
+
const result = await response.json();
|
|
62
|
+
return parseDiscoveredEnvironments(Array.isArray(result.value) ? result.value : [], tenantId, appDisplayName, appName);
|
|
63
|
+
} catch (error) {
|
|
64
|
+
throw new TargetRuntimeError('discovery-failed', 'discovery', true, 'Your environments could not be loaded. Try again.', undefined, error);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
/** Fetch Dataverse environment metadata from the Global Discovery Service. */
|
|
68
|
+
export async function fetchInstanceMetadata(environmentId, region, acquireToken, _bearerToken) {
|
|
44
69
|
const scenario = getAppLogger().trackScenario(HostOperationNames.PlatformFetchInstanceUrl, {
|
|
45
70
|
environmentId,
|
|
46
71
|
region
|
|
47
72
|
});
|
|
48
73
|
try {
|
|
49
|
-
const
|
|
50
|
-
if (
|
|
74
|
+
const cachedMetadata = await getCachedInstanceUrl(environmentId, region);
|
|
75
|
+
if (cachedMetadata) {
|
|
51
76
|
scenario.complete({
|
|
52
77
|
hasInstanceUrl: true,
|
|
53
78
|
cacheHit: true
|
|
54
79
|
});
|
|
55
|
-
return
|
|
80
|
+
return cachedMetadata;
|
|
56
81
|
}
|
|
57
82
|
const url = buildGlobalDiscoveryUrl(environmentId, region);
|
|
58
83
|
const token = await acquireToken([globalDiscoveryScope(region)]);
|
|
@@ -70,20 +95,30 @@ export async function fetchInstanceUrl(environmentId, region, acquireToken, _bea
|
|
|
70
95
|
throw new Error(`fetchInstanceUrl: Global Discovery returned ${response.status} ${response.statusText}`);
|
|
71
96
|
}
|
|
72
97
|
const result = await response.json();
|
|
73
|
-
const
|
|
98
|
+
const instance = result.value?.find(instance => instance.EnvironmentId?.toLowerCase() === environmentId.toLowerCase());
|
|
99
|
+
const instanceUrl = instance?.ApiUrl;
|
|
100
|
+
const telemetryLocation = tryResolveTelemetryLocationFromDataverseRegion(instance?.Region);
|
|
74
101
|
if (instanceUrl) {
|
|
75
|
-
await cacheInstanceUrl(environmentId, region, instanceUrl);
|
|
102
|
+
await cacheInstanceUrl(environmentId, region, instanceUrl, telemetryLocation);
|
|
76
103
|
}
|
|
77
104
|
scenario.complete({
|
|
78
105
|
hasInstanceUrl: instanceUrl !== undefined,
|
|
79
106
|
cacheHit: false,
|
|
80
107
|
...getHttpCorrelationProperties(response.headers)
|
|
81
108
|
});
|
|
82
|
-
return instanceUrl
|
|
109
|
+
return instanceUrl ? {
|
|
110
|
+
instanceUrl,
|
|
111
|
+
telemetryLocation
|
|
112
|
+
} : undefined;
|
|
83
113
|
} catch (error) {
|
|
84
114
|
scenario.fail(undefined, {
|
|
85
115
|
errorName: getSafeErrorName(error)
|
|
86
116
|
});
|
|
87
117
|
throw error;
|
|
88
118
|
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/** Fetch only the Dataverse instance URL for callers that do not need region metadata. */
|
|
122
|
+
export async function fetchInstanceUrl(environmentId, region, acquireToken, bearerToken) {
|
|
123
|
+
return (await fetchInstanceMetadata(environmentId, region, acquireToken, bearerToken))?.instanceUrl;
|
|
89
124
|
}
|