@microsoft/power-apps-native-host 0.2.22 → 0.3.0
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 +162 -26
- package/compatibility/current.json +87 -0
- package/lib/NOTICE.txt +140 -1226
- package/lib/commonjs/auth/AuthContext.js +74 -18
- package/lib/commonjs/auth/AuthContext.web.js +32 -0
- package/lib/commonjs/auth/completePowerAppsAuthSession.js +16 -0
- package/lib/commonjs/bridge/NativePowerAppsBridge.js +20 -17
- package/lib/commonjs/bridge/PowerPagesBridge.js +157 -0
- package/lib/commonjs/bridge/bridgeState.js +40 -0
- package/lib/commonjs/connections/ConnectionSetupScreen.js +459 -80
- package/lib/commonjs/connections/PowerPlatformConnectionApi.js +946 -0
- package/lib/commonjs/connections/connectionMetadata.js +127 -0
- package/lib/commonjs/connections/connectionState.js +96 -0
- package/lib/commonjs/connections/disambiguate.js +19 -11
- package/lib/commonjs/connections/resolveConnections.js +347 -0
- package/lib/commonjs/connections/types.js +106 -0
- package/lib/commonjs/{platform → connections}/useConnectionRefs.js +128 -65
- package/lib/commonjs/connections/useConnectionSetup.js +111 -52
- package/lib/commonjs/controls/FilePicker.js +6 -0
- package/lib/commonjs/controls/ImagePicker.js +8 -0
- package/lib/commonjs/index.js +188 -6
- package/lib/commonjs/logger.js +9 -6
- package/lib/commonjs/offline/createOfflineHostActivationCallbacks.js +61 -0
- package/lib/commonjs/offline/useOfflineIntegration.js +12 -0
- package/lib/commonjs/offline/useOfflineIntegration.web.js +11 -0
- package/lib/commonjs/offline/useOfflineIntegrationWithStartupGate.js +46 -0
- package/lib/commonjs/offline/useOfflineIntegrationWithStartupGate.web.js +13 -0
- package/lib/commonjs/platform/DataverseDataSources.js +14 -0
- package/lib/commonjs/platform/DataverseGlobalDiscoveryApi.js +49 -0
- package/lib/commonjs/platform/GlobalDiscovery.js +51 -0
- package/lib/commonjs/platform/PowerPlatformEnvironmentApi.js +6 -106
- package/lib/commonjs/provider/PowerAppsHostProvider.js +156 -25
- package/lib/commonjs/provider/PowerAppsHostProvider.web.js +41 -0
- package/lib/commonjs/provider/PowerAppsProvider.js +229 -133
- package/lib/commonjs/provider/SessionDetailsGesture.js +216 -0
- package/lib/commonjs/telemetry/OfflineSdkTelemetry.js +95 -0
- package/lib/commonjs/telemetry/OfflineSdkTelemetry.web.js +9 -0
- package/lib/commonjs/telemetry/index.js +22 -3
- package/lib/module/auth/AuthContext.js +74 -18
- package/lib/module/auth/AuthContext.web.js +26 -0
- package/lib/module/auth/completePowerAppsAuthSession.js +11 -0
- package/lib/module/bridge/NativePowerAppsBridge.js +21 -18
- package/lib/module/bridge/PowerPagesBridge.js +151 -0
- package/lib/module/bridge/bridgeState.js +34 -0
- package/lib/module/connections/ConnectionSetupScreen.js +460 -81
- package/lib/module/connections/PowerPlatformConnectionApi.js +932 -0
- package/lib/module/connections/connectionMetadata.js +112 -0
- package/lib/module/connections/connectionState.js +82 -0
- package/lib/module/connections/disambiguate.js +19 -12
- package/lib/module/connections/resolveConnections.js +342 -0
- package/lib/module/connections/types.js +91 -0
- package/lib/module/{platform → connections}/useConnectionRefs.js +129 -66
- package/lib/module/connections/useConnectionSetup.js +113 -54
- package/lib/module/controls/FilePicker.js +6 -0
- package/lib/module/controls/ImagePicker.js +8 -0
- package/lib/module/index.js +14 -6
- package/lib/module/logger.js +8 -6
- package/lib/module/offline/createOfflineHostActivationCallbacks.js +55 -0
- package/lib/module/offline/useOfflineIntegration.js +3 -0
- package/lib/module/offline/useOfflineIntegration.web.js +7 -0
- package/lib/module/offline/useOfflineIntegrationWithStartupGate.js +42 -0
- package/lib/module/offline/useOfflineIntegrationWithStartupGate.web.js +9 -0
- package/lib/module/platform/DataverseDataSources.js +10 -0
- package/lib/module/platform/DataverseGlobalDiscoveryApi.js +45 -0
- package/lib/module/platform/GlobalDiscovery.js +45 -0
- package/lib/module/platform/PowerPlatformEnvironmentApi.js +5 -104
- package/lib/module/provider/PowerAppsHostProvider.js +159 -28
- package/lib/module/provider/PowerAppsHostProvider.web.js +36 -0
- package/lib/module/provider/PowerAppsProvider.js +233 -137
- package/lib/module/provider/SessionDetailsGesture.js +211 -0
- package/lib/module/telemetry/OfflineSdkTelemetry.js +91 -0
- package/lib/module/telemetry/OfflineSdkTelemetry.web.js +5 -0
- package/lib/module/telemetry/index.js +15 -4
- package/lib/typescript/commonjs/auth/AuthContext.d.ts +3 -1
- package/lib/typescript/commonjs/auth/AuthContext.web.d.ts +21 -0
- package/lib/typescript/commonjs/auth/completePowerAppsAuthSession.d.ts +8 -0
- package/lib/typescript/commonjs/bridge/PowerPagesBridge.d.ts +18 -0
- package/lib/typescript/commonjs/bridge/bridgeState.d.ts +5 -0
- package/lib/typescript/commonjs/connections/ConnectionSetupScreen.d.ts +28 -7
- package/lib/typescript/commonjs/{platform → connections}/PowerPlatformConnectionApi.d.ts +52 -43
- package/lib/typescript/commonjs/connections/connectionMetadata.d.ts +66 -0
- package/lib/typescript/commonjs/connections/connectionState.d.ts +16 -0
- package/lib/typescript/commonjs/connections/disambiguate.d.ts +4 -2
- package/lib/typescript/commonjs/{platform → connections}/resolveConnections.d.ts +19 -19
- package/lib/typescript/commonjs/connections/types.d.ts +129 -1
- package/lib/typescript/commonjs/{platform → connections}/useConnectionRefs.d.ts +33 -24
- package/lib/typescript/commonjs/connections/useConnectionSetup.d.ts +12 -8
- package/lib/typescript/commonjs/controls/FilePicker.d.ts +7 -1
- package/lib/typescript/commonjs/controls/ImagePicker.d.ts +9 -1
- package/lib/typescript/commonjs/index.d.ts +20 -10
- package/lib/typescript/commonjs/logger.d.ts +4 -4
- package/lib/typescript/commonjs/offline/createOfflineHostActivationCallbacks.d.ts +12 -0
- package/lib/typescript/commonjs/offline/useOfflineIntegration.d.ts +2 -0
- package/lib/typescript/commonjs/offline/useOfflineIntegration.web.d.ts +4 -0
- package/lib/typescript/commonjs/offline/useOfflineIntegrationWithStartupGate.d.ts +15 -0
- package/lib/typescript/commonjs/offline/useOfflineIntegrationWithStartupGate.web.d.ts +6 -0
- package/lib/typescript/commonjs/platform/DataverseDataSources.d.ts +27 -0
- package/lib/typescript/commonjs/platform/DataverseGlobalDiscoveryApi.d.ts +3 -0
- package/lib/typescript/commonjs/platform/GlobalDiscovery.d.ts +4 -0
- package/lib/typescript/commonjs/platform/PowerPlatformEnvironmentApi.d.ts +3 -74
- package/lib/typescript/commonjs/provider/PowerAppsHostProvider.d.ts +29 -18
- package/lib/typescript/commonjs/provider/PowerAppsHostProvider.web.d.ts +5 -0
- package/lib/typescript/commonjs/provider/PowerAppsProvider.d.ts +32 -10
- package/lib/typescript/commonjs/provider/SessionDetailsGesture.d.ts +13 -0
- package/lib/typescript/commonjs/telemetry/OfflineSdkTelemetry.d.ts +15 -0
- package/lib/typescript/commonjs/telemetry/OfflineSdkTelemetry.web.d.ts +2 -0
- package/lib/typescript/commonjs/telemetry/index.d.ts +13 -1
- package/lib/typescript/module/auth/AuthContext.d.ts +3 -1
- package/lib/typescript/module/auth/AuthContext.web.d.ts +21 -0
- package/lib/typescript/module/auth/completePowerAppsAuthSession.d.ts +8 -0
- package/lib/typescript/module/bridge/PowerPagesBridge.d.ts +18 -0
- package/lib/typescript/module/bridge/bridgeState.d.ts +5 -0
- package/lib/typescript/module/connections/ConnectionSetupScreen.d.ts +28 -7
- package/lib/typescript/module/{platform → connections}/PowerPlatformConnectionApi.d.ts +52 -43
- package/lib/typescript/module/connections/connectionMetadata.d.ts +66 -0
- package/lib/typescript/module/connections/connectionState.d.ts +16 -0
- package/lib/typescript/module/connections/disambiguate.d.ts +4 -2
- package/lib/typescript/module/{platform → connections}/resolveConnections.d.ts +19 -19
- package/lib/typescript/module/connections/types.d.ts +129 -1
- package/lib/typescript/module/{platform → connections}/useConnectionRefs.d.ts +33 -24
- package/lib/typescript/module/connections/useConnectionSetup.d.ts +12 -8
- package/lib/typescript/module/controls/FilePicker.d.ts +7 -1
- package/lib/typescript/module/controls/ImagePicker.d.ts +9 -1
- package/lib/typescript/module/index.d.ts +24 -14
- package/lib/typescript/module/logger.d.ts +4 -4
- package/lib/typescript/module/offline/createOfflineHostActivationCallbacks.d.ts +12 -0
- package/lib/typescript/module/offline/useOfflineIntegration.d.ts +2 -0
- package/lib/typescript/module/offline/useOfflineIntegration.web.d.ts +4 -0
- package/lib/typescript/module/offline/useOfflineIntegrationWithStartupGate.d.ts +15 -0
- package/lib/typescript/module/offline/useOfflineIntegrationWithStartupGate.web.d.ts +6 -0
- package/lib/typescript/module/platform/DataverseDataSources.d.ts +27 -0
- package/lib/typescript/module/platform/DataverseGlobalDiscoveryApi.d.ts +3 -0
- package/lib/typescript/module/platform/GlobalDiscovery.d.ts +4 -0
- package/lib/typescript/module/platform/PowerPlatformEnvironmentApi.d.ts +3 -74
- package/lib/typescript/module/provider/PowerAppsHostProvider.d.ts +29 -18
- package/lib/typescript/module/provider/PowerAppsHostProvider.web.d.ts +5 -0
- package/lib/typescript/module/provider/PowerAppsProvider.d.ts +34 -12
- package/lib/typescript/module/provider/SessionDetailsGesture.d.ts +13 -0
- package/lib/typescript/module/telemetry/OfflineSdkTelemetry.d.ts +15 -0
- package/lib/typescript/module/telemetry/OfflineSdkTelemetry.web.d.ts +2 -0
- package/lib/typescript/module/telemetry/index.d.ts +13 -1
- package/package.json +43 -18
- package/scripts/build-ios-base.js +66 -27
- package/scripts/bundle.js +212 -5
- package/scripts/generate-connector-schemas.js +8 -6
- package/scripts/metro-logger.js +213 -0
- package/scripts/start-web.mjs +137 -0
- package/scripts/upgrade/runtime.js +463 -0
- package/scripts/upgrade/system.js +244 -0
- package/scripts/upgrade-authoring.js +534 -0
- package/scripts/upgrade.js +188 -0
- package/scripts/wrap.js +536 -0
- package/lib/commonjs/platform/PowerPlatformConnectionApi.js +0 -606
- package/lib/commonjs/platform/resolveConnections.js +0 -264
- package/lib/module/platform/PowerPlatformConnectionApi.js +0 -595
- package/lib/module/platform/resolveConnections.js +0 -259
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
import { getConnectorId } from "./types.js";
|
|
4
|
+
export function parseConnectionParameters(parameters) {
|
|
5
|
+
return Object.entries(parameters ?? {}).flatMap(([name, parameter]) => {
|
|
6
|
+
if (!parameter.uiDefinition) return [];
|
|
7
|
+
const constraints = parameter.uiDefinition.constraints;
|
|
8
|
+
const type = (parameter.type ?? 'string').toLowerCase();
|
|
9
|
+
const supportedType = type === 'string' || type === 'securestring' || type === 'boolean' || type === 'integer' || type === 'clientcertificate' ? type : null;
|
|
10
|
+
if (!supportedType) return [];
|
|
11
|
+
return [{
|
|
12
|
+
name,
|
|
13
|
+
displayName: parameter.uiDefinition.displayName ?? name,
|
|
14
|
+
type: supportedType,
|
|
15
|
+
required: constraints?.required === true || constraints?.required === 'true',
|
|
16
|
+
hidden: constraints?.hidden === true || constraints?.hidden === 'true',
|
|
17
|
+
capabilities: constraints?.capability,
|
|
18
|
+
allowedValues: constraints?.allowedValues?.filter(item => Boolean(item.value)).map(item => ({
|
|
19
|
+
value: item.value,
|
|
20
|
+
displayName: item.displayName ?? item.value
|
|
21
|
+
})),
|
|
22
|
+
defaultValue: parameter.defaultValue
|
|
23
|
+
}];
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
export function buildConnectionParameterSets(connectionParameters, parameterSets) {
|
|
27
|
+
const declaredSets = (parameterSets ?? []).filter(item => Boolean(item.name)).map(item => ({
|
|
28
|
+
name: item.name,
|
|
29
|
+
displayName: item.uiDefinition?.displayName ?? item.name,
|
|
30
|
+
parameters: parseConnectionParameters(item.connectionParameters ?? item.parameters),
|
|
31
|
+
requiresApiConsent: hasOAuthParameter(item.connectionParameters ?? item.parameters),
|
|
32
|
+
gatewayParameterName: Object.entries(item.connectionParameters ?? item.parameters ?? {}).find(([, parameter]) => parameter.type?.toLowerCase() === 'gatewaysetting')?.[0]
|
|
33
|
+
}));
|
|
34
|
+
return declaredSets.length > 0 ? declaredSets : [{
|
|
35
|
+
name: undefined,
|
|
36
|
+
displayName: 'Default',
|
|
37
|
+
parameters: parseConnectionParameters(connectionParameters),
|
|
38
|
+
requiresApiConsent: hasOAuthParameter(connectionParameters),
|
|
39
|
+
gatewayParameterName: Object.entries(connectionParameters ?? {}).find(([, parameter]) => parameter.type?.toLowerCase() === 'gatewaysetting')?.[0]
|
|
40
|
+
}];
|
|
41
|
+
}
|
|
42
|
+
export function hasOAuthParameter(parameters) {
|
|
43
|
+
return Object.values(parameters ?? {}).some(parameter => parameter.type?.toLowerCase() === 'oauthsetting');
|
|
44
|
+
}
|
|
45
|
+
export function buildConnectionUserConsentUrl(environmentEndpoint, connectorId, connectionId) {
|
|
46
|
+
return `${environmentEndpoint}/connectivity/connectors/${encodeURIComponent(getConnectorId(connectorId))}` + `/connections/${encodeURIComponent(connectionId)}/userConsent?api-version=1`;
|
|
47
|
+
}
|
|
48
|
+
export function getAppLaunchFailureMessage(status, statusText, responseText) {
|
|
49
|
+
if (status === 404) return null;
|
|
50
|
+
return `Unable to load the published app (HTTP ${status}): ${responseText || statusText}`;
|
|
51
|
+
}
|
|
52
|
+
export function isAppLaunchAccessDenied(status, responseText) {
|
|
53
|
+
if (status !== 403) return false;
|
|
54
|
+
try {
|
|
55
|
+
const body = JSON.parse(responseText);
|
|
56
|
+
return body.error?.code === 'PowerAppsNoAccess' || body.error?.details?.some(detail => detail.code === 'PowerAppsNoAccess') === true;
|
|
57
|
+
} catch {
|
|
58
|
+
return false;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
export function buildConsentLinkDefinition(redirectUrl) {
|
|
62
|
+
return {
|
|
63
|
+
redirectUrl
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
export function serializeConnectionParameters(input) {
|
|
67
|
+
const parameters = {
|
|
68
|
+
...(input?.parameters ?? {}),
|
|
69
|
+
...(input?.gatewayObjectId && input.gatewayParameterName ? {
|
|
70
|
+
[input.gatewayParameterName]: {
|
|
71
|
+
gatewayObjectId: input.gatewayObjectId
|
|
72
|
+
}
|
|
73
|
+
} : {})
|
|
74
|
+
};
|
|
75
|
+
if (!input?.parameterSetName) return {
|
|
76
|
+
connectionParameters: parameters
|
|
77
|
+
};
|
|
78
|
+
return {
|
|
79
|
+
connectionParametersSet: {
|
|
80
|
+
name: input.parameterSetName,
|
|
81
|
+
values: Object.fromEntries(Object.entries(parameters).map(([name, value]) => [name, {
|
|
82
|
+
value
|
|
83
|
+
}]))
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
export function buildConnectionUsageUrl(environmentBaseUrl, environmentId, usage) {
|
|
88
|
+
return `${environmentBaseUrl}/powerapps/apis/${encodeURIComponent(getConnectorId(usage.apiId))}` + `/connections/${encodeURIComponent(usage.connectionId)}/usages` + `?api-version=1&$filter=environment eq '${environmentId}'`;
|
|
89
|
+
}
|
|
90
|
+
export function buildConnectionUsageRequest(appId, usage) {
|
|
91
|
+
const resourceId = appId.startsWith('/providers/') ? appId : `/providers/Microsoft.PowerApps/apps/${appId}`;
|
|
92
|
+
return {
|
|
93
|
+
properties: {
|
|
94
|
+
reference: usage.localRefId,
|
|
95
|
+
resourceId,
|
|
96
|
+
appActions: usage.appActions
|
|
97
|
+
}
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
export function buildScopeConsentUrl(environmentBaseUrl, appId) {
|
|
101
|
+
const appName = appId.split('/').filter(Boolean).pop() ?? appId;
|
|
102
|
+
return `${environmentBaseUrl}/powerapps/apps/${encodeURIComponent(appName)}` + '/reportScopesConsent?api-version=1';
|
|
103
|
+
}
|
|
104
|
+
export function buildScopeConsentRequest(usages) {
|
|
105
|
+
return {
|
|
106
|
+
consents: usages.map(usage => ({
|
|
107
|
+
connectionId: usage.connectionId,
|
|
108
|
+
consentType: 'userConsent',
|
|
109
|
+
scopes: usage.apiScopes
|
|
110
|
+
}))
|
|
111
|
+
};
|
|
112
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
import { normalizeApiId } from "./types.js";
|
|
4
|
+
import { connectionIdsEqual } from "./disambiguate.js";
|
|
5
|
+
export function isTransientConnectionConsentStatus(status) {
|
|
6
|
+
return status === 408 || status === 429 || status >= 500;
|
|
7
|
+
}
|
|
8
|
+
export function shouldGrantConnectionUserConsent(requirement, runningInDevPlayer) {
|
|
9
|
+
return runningInDevPlayer || !requirement.sharedConnectionId;
|
|
10
|
+
}
|
|
11
|
+
export function getConnectionNamesRequiringAclGrant(connectionRefs, connectionRequirements) {
|
|
12
|
+
const connectionNames = [];
|
|
13
|
+
const seen = new Set();
|
|
14
|
+
for (const requirement of connectionRequirements) {
|
|
15
|
+
if (!shouldGrantConnectionUserConsent(requirement, false)) continue;
|
|
16
|
+
const connectionName = requirement.dataSources.map(dataSource => connectionRefs[dataSource]?.connectionName).find(selectedName => Boolean(selectedName));
|
|
17
|
+
if (!connectionName || seen.has(connectionName)) continue;
|
|
18
|
+
seen.add(connectionName);
|
|
19
|
+
connectionNames.push(connectionName);
|
|
20
|
+
}
|
|
21
|
+
return connectionNames;
|
|
22
|
+
}
|
|
23
|
+
export function invalidateFailedAclGrants(result, failedConnectionNames) {
|
|
24
|
+
if (failedConnectionNames.size === 0) return;
|
|
25
|
+
for (const requirement of result.connectionRequirements) {
|
|
26
|
+
const selected = requirement.dataSources.map(dataSource => result.connectionRefs[dataSource]).find(Boolean);
|
|
27
|
+
if (!selected || !failedConnectionNames.has(selected.connectionName)) continue;
|
|
28
|
+
for (const dataSource of requirement.dataSources) {
|
|
29
|
+
const reference = result.connectionRefs[dataSource];
|
|
30
|
+
if (reference) reference.connectionName = '';
|
|
31
|
+
}
|
|
32
|
+
if (!result.missingConnectionReferences.some(item => item.localRefId === requirement.localRefId)) {
|
|
33
|
+
result.missingConnectionReferences.push(requirement);
|
|
34
|
+
}
|
|
35
|
+
if (!result.missingConnectors.includes(requirement.apiId)) {
|
|
36
|
+
result.missingConnectors.push(requirement.apiId);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
export function selectLaunchConnection(result, localRefId, connectionId) {
|
|
41
|
+
const requirement = result.connectionRequirements.find(item => item.localRefId === localRefId);
|
|
42
|
+
const candidate = result.connectionCandidates[localRefId]?.find(item => connectionIdsEqual(item.id, connectionId));
|
|
43
|
+
if (!requirement || !candidate) return false;
|
|
44
|
+
for (const dataSource of requirement.dataSources) {
|
|
45
|
+
const reference = result.connectionRefs[dataSource];
|
|
46
|
+
if (reference) reference.connectionName = candidate.id;
|
|
47
|
+
}
|
|
48
|
+
result.missingConnectionReferences = result.missingConnectionReferences.filter(item => item.localRefId !== localRefId);
|
|
49
|
+
if (!result.missingConnectionReferences.some(item => item.apiId === requirement.apiId)) {
|
|
50
|
+
result.missingConnectors = result.missingConnectors.filter(apiId => apiId !== requirement.apiId);
|
|
51
|
+
}
|
|
52
|
+
return true;
|
|
53
|
+
}
|
|
54
|
+
export function getUnregisteredConnectionRequirements(result, usages = result.registeredConnectionUsages) {
|
|
55
|
+
return result.connectionRequirements.filter(requirement => {
|
|
56
|
+
const selectedConnectionId = requirement.dataSources.map(dataSource => result.connectionRefs[dataSource]?.connectionName).find(Boolean);
|
|
57
|
+
if (!selectedConnectionId) return true;
|
|
58
|
+
return !usages.some(usage => usage.reference.toLowerCase() === requirement.localRefId.toLowerCase() && normalizeApiId(usage.apiId) === requirement.apiId && connectionIdsEqual(usage.connectionId, selectedConnectionId) && (usage.appActions.length === 0 || (requirement.appActions ?? []).every(action => usage.appActions.includes(action))));
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
export function applyRegisteredConnectionSelections(result, usages) {
|
|
62
|
+
for (const requirement of result.connectionRequirements) {
|
|
63
|
+
const usage = usages.find(item => item.reference.toLowerCase() === requirement.localRefId.toLowerCase() && normalizeApiId(item.apiId) === requirement.apiId);
|
|
64
|
+
if (!usage) continue;
|
|
65
|
+
const candidate = result.connectionCandidates[requirement.localRefId]?.find(item => connectionIdsEqual(item.id, usage.connectionId));
|
|
66
|
+
if (candidate) selectLaunchConnection(result, requirement.localRefId, candidate.id);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
export function hasRegisteredConnectionConsent(result, usages = result.registeredConnectionUsages) {
|
|
70
|
+
return getUnregisteredConnectionRequirements(result, usages).length === 0;
|
|
71
|
+
}
|
|
72
|
+
export function applyRegisteredConnectionUsages(result, usages) {
|
|
73
|
+
applyRegisteredConnectionSelections(result, usages);
|
|
74
|
+
result.registeredConnectionUsages = usages;
|
|
75
|
+
result.requiresConnectionConsent = getUnregisteredConnectionRequirements(result, usages).length > 0;
|
|
76
|
+
}
|
|
77
|
+
export function shouldPersistConnectionUsages(runningInDevPlayer, appId) {
|
|
78
|
+
return !runningInDevPlayer && Boolean(appId);
|
|
79
|
+
}
|
|
80
|
+
export function shouldShowConnectionReview(runningInDevPlayer, requiresConnectionConsent, hasResourceConsents) {
|
|
81
|
+
return !runningInDevPlayer && (requiresConnectionConsent || hasResourceConsents);
|
|
82
|
+
}
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
* other web-host dependency.
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
|
-
import { ConnectionStatus } from "./types.js";
|
|
14
|
+
import { ConnectionStatus, normalizeApiId } from "./types.js";
|
|
15
15
|
/**
|
|
16
16
|
* Disambiguate a set of connection references against the user's available
|
|
17
17
|
* connection instances.
|
|
@@ -30,11 +30,20 @@ import { ConnectionStatus } from "./types.js";
|
|
|
30
30
|
export function disambiguateConnections(connectionReferences, allConnectionsByApiId) {
|
|
31
31
|
const resolutions = [];
|
|
32
32
|
for (const [localRefId, ref] of Object.entries(connectionReferences)) {
|
|
33
|
-
const apiId = ref.id;
|
|
34
|
-
const all = allConnectionsByApiId[apiId] ?? [];
|
|
33
|
+
const apiId = normalizeApiId(ref.id);
|
|
34
|
+
const all = allConnectionsByApiId[apiId] ?? allConnectionsByApiId[ref.id] ?? [];
|
|
35
35
|
const connected = all.filter(c => c.status === ConnectionStatus.Connected);
|
|
36
36
|
let result;
|
|
37
|
-
if (
|
|
37
|
+
if (ref.sharedConnectionId) {
|
|
38
|
+
const sharedId = extractConnectionId(ref.sharedConnectionId);
|
|
39
|
+
const shared = connected.find(connection => connectionIdsEqual(connection.id, sharedId));
|
|
40
|
+
result = shared ? {
|
|
41
|
+
kind: 'resolved',
|
|
42
|
+
connectionId: shared.id
|
|
43
|
+
} : {
|
|
44
|
+
kind: 'missing'
|
|
45
|
+
};
|
|
46
|
+
} else if (connected.length === 1) {
|
|
38
47
|
result = {
|
|
39
48
|
kind: 'resolved',
|
|
40
49
|
connectionId: connected[0].id
|
|
@@ -68,16 +77,14 @@ export function disambiguateConnections(connectionReferences, allConnectionsByAp
|
|
|
68
77
|
* "abc12345def6789012345678901234ab" (bare UUID, no dashes)
|
|
69
78
|
* "abc12345-def6-7890-1234-5678901234ab" (UUID with dashes)
|
|
70
79
|
*
|
|
71
|
-
* Returns the
|
|
72
|
-
*
|
|
80
|
+
* Returns the terminal resource name exactly as supplied by Power Platform.
|
|
81
|
+
* Some connectors require dashed GUIDs while others use compact IDs.
|
|
73
82
|
*/
|
|
74
83
|
export function extractConnectionId(rawName) {
|
|
75
|
-
// Match a standard UUID (with or without dashes) at the end of the string.
|
|
76
|
-
const match = rawName.match(/([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})$/i);
|
|
77
|
-
if (match) {
|
|
78
|
-
return match[1].replace(/-/g, '');
|
|
79
|
-
}
|
|
80
|
-
// Some connections use a 32-char hex ID without dashes, after a slash.
|
|
81
84
|
const slashIdx = rawName.lastIndexOf('/');
|
|
82
85
|
return slashIdx >= 0 ? rawName.slice(slashIdx + 1) : rawName;
|
|
86
|
+
}
|
|
87
|
+
export function connectionIdsEqual(left, right) {
|
|
88
|
+
const normalize = value => extractConnectionId(value).replace(/-/g, '').toLowerCase();
|
|
89
|
+
return normalize(left) === normalize(right);
|
|
83
90
|
}
|
|
@@ -0,0 +1,342 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
/*!
|
|
4
|
+
* power-apps-native-host — resolveConnections
|
|
5
|
+
*
|
|
6
|
+
* Pure async helpers for the two connection-resolution strategies used by
|
|
7
|
+
* useConnectionRefs:
|
|
8
|
+
*
|
|
9
|
+
* • resolveViaStaticRefs — per-connector resolution using the static
|
|
10
|
+
* connectionReferences map from power.config.json. Used only by Dev Player
|
|
11
|
+
* for unpublished local apps. Handles pre-disambiguation ACL grants so
|
|
12
|
+
* inaccessible connections are excluded before the user is asked to pick.
|
|
13
|
+
*
|
|
14
|
+
* • applyAclGrants — post-resolution user consent used by the fast path
|
|
15
|
+
* (production APKs via fetchAppLaunchData).
|
|
16
|
+
*
|
|
17
|
+
* Both helpers return / consume the shared AppLaunchResult shape so the
|
|
18
|
+
* hook orchestrator can handle them uniformly.
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
import { fetchConnectorMetadata, grantConnectionUserConsent } from "./PowerPlatformConnectionApi.js";
|
|
22
|
+
import { getConnectionNamesRequiringAclGrant, shouldGrantConnectionUserConsent } from "./connectionState.js";
|
|
23
|
+
import { connectionUserConsentScope, buildEnvironmentEndpoint } from "../platform/PowerPlatformEnvironmentApi.js";
|
|
24
|
+
import { fetchInstanceUrl } from "../platform/DataverseGlobalDiscoveryApi.js";
|
|
25
|
+
import { connectionIdsEqual, disambiguateConnections, extractConnectionId } from "../connections/disambiguate.js";
|
|
26
|
+
import { buildConnectionRequirements, ConnectionStatus, findConnectionDataSourceConflicts, getConnectionDatasetName, getFlowConnectionName, getFlowDataSourceNames, isCompositeConnectionApiId, getConnectorId, normalizeApiId } from "./types.js";
|
|
27
|
+
import { platformHttpClient } from "../telemetry/index.js";
|
|
28
|
+
const API_VERSION = '1';
|
|
29
|
+
|
|
30
|
+
// ---------------------------------------------------------------------------
|
|
31
|
+
// ConnectorSchema
|
|
32
|
+
// ---------------------------------------------------------------------------
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Connector schema entry generated by `generate-connector-schemas`.
|
|
36
|
+
* Keyed by data source name in the `schemaMap` passed to `useConnectionRefs`.
|
|
37
|
+
*/
|
|
38
|
+
|
|
39
|
+
// ---------------------------------------------------------------------------
|
|
40
|
+
// resolveViaStaticRefs
|
|
41
|
+
// ---------------------------------------------------------------------------
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Resolve connections from the static `connectionReferences` map.
|
|
45
|
+
*
|
|
46
|
+
* Steps:
|
|
47
|
+
* 1. Build an apiId → schema lookup.
|
|
48
|
+
* 2. In parallel: fetch the Dataverse instanceUrl and all connection instances
|
|
49
|
+
* for every connector.
|
|
50
|
+
* 3. If `isvClientId` is set, run pre-disambiguation user consent — connections
|
|
51
|
+
* where the grant fails are marked `NeedsAclGrant` and excluded from
|
|
52
|
+
* resolution, prompting the user to create a new connection.
|
|
53
|
+
* 4. Disambiguate: resolve single matches, invoke callbacks for ambiguous /
|
|
54
|
+
* missing cases, perform one-shot retries when `onMissingConnection` returns
|
|
55
|
+
* `true`.
|
|
56
|
+
* 5. Build and return an `AppLaunchResult`-shaped object.
|
|
57
|
+
*/
|
|
58
|
+
export async function resolveViaStaticRefs({
|
|
59
|
+
connectionReferences,
|
|
60
|
+
schemaMap,
|
|
61
|
+
environmentId,
|
|
62
|
+
region,
|
|
63
|
+
acquireToken,
|
|
64
|
+
token,
|
|
65
|
+
isvClientId,
|
|
66
|
+
onDisambiguationNeeded,
|
|
67
|
+
onMissingConnection
|
|
68
|
+
}) {
|
|
69
|
+
const directConnectionReferences = Object.fromEntries(Object.entries(connectionReferences).filter(([, reference]) => !isCompositeConnectionApiId(reference.id)));
|
|
70
|
+
const flowConnectionReferences = Object.fromEntries(Object.entries(connectionReferences).filter(([, reference]) => getConnectorId(reference.id) === 'shared_logicflows'));
|
|
71
|
+
// Build apiId → schema lookup from the static refs.
|
|
72
|
+
const schemaByApiId = Object.fromEntries(Object.values(directConnectionReferences).map(ref => {
|
|
73
|
+
const schema = ref.dataSources.map(ds => schemaMap[ds]).find(Boolean);
|
|
74
|
+
return schema ? [normalizeApiId(ref.id), schema] : null;
|
|
75
|
+
}).filter(entry => entry !== null));
|
|
76
|
+
await Promise.all(Object.values({
|
|
77
|
+
...directConnectionReferences,
|
|
78
|
+
...flowConnectionReferences
|
|
79
|
+
}).map(async reference => {
|
|
80
|
+
const apiId = normalizeApiId(reference.id);
|
|
81
|
+
if (schemaByApiId[apiId]) return;
|
|
82
|
+
const metadata = await fetchConnectorMetadata(getConnectorId(reference.id), environmentId, region, token);
|
|
83
|
+
if (!metadata?.primaryRuntimeUrl) return;
|
|
84
|
+
schemaByApiId[apiId] = {
|
|
85
|
+
name: metadata.name,
|
|
86
|
+
properties: {
|
|
87
|
+
primaryRuntimeUrl: metadata.primaryRuntimeUrl,
|
|
88
|
+
scopes: metadata.scopes
|
|
89
|
+
}
|
|
90
|
+
};
|
|
91
|
+
}));
|
|
92
|
+
const base = buildEnvironmentEndpoint(environmentId, region);
|
|
93
|
+
const headers = {
|
|
94
|
+
Authorization: `Bearer ${token}`
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
// Fetch Dataverse instanceUrl and all connection instances in parallel.
|
|
98
|
+
const [instanceUrl, ...connResults] = await Promise.all([fetchInstanceUrl(environmentId, region, acquireToken, token), ...Object.entries(directConnectionReferences).map(async ([localRefId, ref]) => {
|
|
99
|
+
const apiId = normalizeApiId(ref.id);
|
|
100
|
+
const schema = schemaByApiId[apiId];
|
|
101
|
+
if (!schema) {
|
|
102
|
+
return {
|
|
103
|
+
localRefId,
|
|
104
|
+
apiId,
|
|
105
|
+
dataSources: ref.dataSources,
|
|
106
|
+
instances: []
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
const url = `${base}/connectivity/connectors/${schema.name}/connections` + `?api-version=${API_VERSION}&$filter=environment eq '${environmentId}'`;
|
|
110
|
+
try {
|
|
111
|
+
const res = await platformHttpClient.request(url, {
|
|
112
|
+
headers
|
|
113
|
+
});
|
|
114
|
+
if (!res.ok) {
|
|
115
|
+
return {
|
|
116
|
+
localRefId,
|
|
117
|
+
apiId,
|
|
118
|
+
dataSources: ref.dataSources,
|
|
119
|
+
instances: []
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
const body = await res.json();
|
|
123
|
+
const instances = (body.value ?? []).map(c => {
|
|
124
|
+
const connected = c.properties?.statuses?.[0]?.status === 'Connected';
|
|
125
|
+
return {
|
|
126
|
+
id: extractConnectionId(c.name),
|
|
127
|
+
name: c.name,
|
|
128
|
+
apiId,
|
|
129
|
+
ownerId: c.properties?.createdBy?.id,
|
|
130
|
+
status: connected ? ConnectionStatus.Connected : ConnectionStatus.NotConnected,
|
|
131
|
+
displayName: c.properties?.displayName,
|
|
132
|
+
isAuthenticated: connected
|
|
133
|
+
};
|
|
134
|
+
});
|
|
135
|
+
return {
|
|
136
|
+
localRefId,
|
|
137
|
+
apiId,
|
|
138
|
+
dataSources: ref.dataSources,
|
|
139
|
+
instances
|
|
140
|
+
};
|
|
141
|
+
} catch {
|
|
142
|
+
return {
|
|
143
|
+
localRefId,
|
|
144
|
+
apiId,
|
|
145
|
+
dataSources: ref.dataSources,
|
|
146
|
+
instances: []
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
})]);
|
|
150
|
+
|
|
151
|
+
// Pre-disambiguation delegated consent grants (ISV apps only).
|
|
152
|
+
//
|
|
153
|
+
// Connections where the grant fails are marked NeedsAclGrant so
|
|
154
|
+
// disambiguation treats them as unavailable and surfaces the
|
|
155
|
+
// "missing connection" flow, steering the user to create a new one. Fixed
|
|
156
|
+
if (isvClientId) {
|
|
157
|
+
const consentToken = await acquireToken([connectionUserConsentScope(region)]);
|
|
158
|
+
if (consentToken) {
|
|
159
|
+
const attemptedGrants = new Set();
|
|
160
|
+
await Promise.all(connResults.map(async r => {
|
|
161
|
+
const reference = directConnectionReferences[r.localRefId];
|
|
162
|
+
if (!reference || !shouldGrantConnectionUserConsent(reference, true)) return;
|
|
163
|
+
const connId = schemaByApiId[r.apiId]?.name;
|
|
164
|
+
if (!connId) return;
|
|
165
|
+
await Promise.all(r.instances.map(async inst => {
|
|
166
|
+
if (inst.status !== ConnectionStatus.Connected) return;
|
|
167
|
+
if (reference?.sharedConnectionId && !connectionIdsEqual(inst.id, reference.sharedConnectionId)) return;
|
|
168
|
+
const grantKey = `${connId.toLowerCase()}/${inst.id.toLowerCase()}`;
|
|
169
|
+
if (attemptedGrants.has(grantKey)) return;
|
|
170
|
+
attemptedGrants.add(grantKey);
|
|
171
|
+
const ok = await grantConnectionUserConsent(connId, inst.id, environmentId, region, consentToken);
|
|
172
|
+
if (!ok) inst.status = ConnectionStatus.NeedsAclGrant;
|
|
173
|
+
}));
|
|
174
|
+
}));
|
|
175
|
+
} else {
|
|
176
|
+
for (const result of connResults) {
|
|
177
|
+
for (const instance of result.instances) {
|
|
178
|
+
if (instance.status === ConnectionStatus.Connected) {
|
|
179
|
+
instance.status = ConnectionStatus.NeedsAclGrant;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// Disambiguation.
|
|
187
|
+
const allConnectionsByApiId = {};
|
|
188
|
+
for (const r of connResults) {
|
|
189
|
+
allConnectionsByApiId[r.apiId] = r.instances;
|
|
190
|
+
}
|
|
191
|
+
const disambiguationResults = disambiguateConnections(directConnectionReferences, allConnectionsByApiId);
|
|
192
|
+
const resolvedIds = {};
|
|
193
|
+
const unresolvedApiIds = [];
|
|
194
|
+
for (const dr of disambiguationResults) {
|
|
195
|
+
if (dr.result.kind === 'resolved') {
|
|
196
|
+
resolvedIds[dr.localRefId] = dr.result.connectionId;
|
|
197
|
+
} else if (dr.result.kind === 'ambiguous') {
|
|
198
|
+
const picked = onDisambiguationNeeded ? await onDisambiguationNeeded({
|
|
199
|
+
localRefId: dr.localRefId,
|
|
200
|
+
apiId: dr.apiId,
|
|
201
|
+
candidates: dr.result.candidates
|
|
202
|
+
}) : null;
|
|
203
|
+
if (picked) {
|
|
204
|
+
resolvedIds[dr.localRefId] = picked;
|
|
205
|
+
} else {
|
|
206
|
+
resolvedIds[dr.localRefId] = '';
|
|
207
|
+
unresolvedApiIds.push(normalizeApiId(dr.apiId));
|
|
208
|
+
}
|
|
209
|
+
} else {
|
|
210
|
+
// kind === 'missing'
|
|
211
|
+
let retried = false;
|
|
212
|
+
if (onMissingConnection) {
|
|
213
|
+
retried = await onMissingConnection({
|
|
214
|
+
localRefId: dr.localRefId,
|
|
215
|
+
apiId: dr.apiId
|
|
216
|
+
});
|
|
217
|
+
}
|
|
218
|
+
if (retried) {
|
|
219
|
+
// One-shot retry for this specific connector.
|
|
220
|
+
const schema = schemaByApiId[dr.apiId];
|
|
221
|
+
if (schema) {
|
|
222
|
+
try {
|
|
223
|
+
const url = `${base}/connectivity/connectors/${schema.name}/connections` + `?api-version=${API_VERSION}&$filter=environment eq '${environmentId}'`;
|
|
224
|
+
const res = await platformHttpClient.request(url, {
|
|
225
|
+
headers
|
|
226
|
+
});
|
|
227
|
+
if (res.ok) {
|
|
228
|
+
const body = await res.json();
|
|
229
|
+
const retryInstances = (body.value ?? []).map(c => {
|
|
230
|
+
const connected = c.properties?.statuses?.[0]?.status === 'Connected';
|
|
231
|
+
return {
|
|
232
|
+
id: extractConnectionId(c.name),
|
|
233
|
+
name: c.name,
|
|
234
|
+
apiId: dr.apiId,
|
|
235
|
+
status: connected ? ConnectionStatus.Connected : ConnectionStatus.NotConnected,
|
|
236
|
+
isAuthenticated: connected
|
|
237
|
+
};
|
|
238
|
+
});
|
|
239
|
+
const connected = retryInstances.find(i => i.status === ConnectionStatus.Connected);
|
|
240
|
+
if (connected) {
|
|
241
|
+
resolvedIds[dr.localRefId] = connected.id;
|
|
242
|
+
continue;
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
} catch {
|
|
246
|
+
// Fall through to missing.
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
resolvedIds[dr.localRefId] = '';
|
|
251
|
+
unresolvedApiIds.push(normalizeApiId(dr.apiId));
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
const dataverseOrgUrl = (instanceUrl ?? '').replace(/\/$/, '');
|
|
255
|
+
|
|
256
|
+
// Build the final AppConnectionReference map.
|
|
257
|
+
const connectionRefs = {};
|
|
258
|
+
for (const [localRefId, ref] of Object.entries(directConnectionReferences)) {
|
|
259
|
+
const normalizedApiId = normalizeApiId(ref.id);
|
|
260
|
+
const schema = schemaByApiId[normalizedApiId];
|
|
261
|
+
const connectionName = resolvedIds[localRefId] ?? '';
|
|
262
|
+
const isDataverse = ref.id.includes('commondataserviceforapps');
|
|
263
|
+
const entry = {
|
|
264
|
+
apiId: normalizedApiId,
|
|
265
|
+
runtimeUrl: schema?.properties.primaryRuntimeUrl ?? '',
|
|
266
|
+
connectionName,
|
|
267
|
+
datasetName: isDataverse ? dataverseOrgUrl : getConnectionDatasetName(ref)
|
|
268
|
+
};
|
|
269
|
+
for (const ds of ref.dataSources) {
|
|
270
|
+
connectionRefs[ds] = entry;
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
for (const ref of Object.values(flowConnectionReferences)) {
|
|
274
|
+
const normalizedApiId = normalizeApiId(ref.id);
|
|
275
|
+
const schema = schemaByApiId[normalizedApiId];
|
|
276
|
+
const entry = {
|
|
277
|
+
apiId: normalizedApiId,
|
|
278
|
+
runtimeUrl: schema?.properties.primaryRuntimeUrl ?? '',
|
|
279
|
+
connectionName: getFlowConnectionName(ref),
|
|
280
|
+
datasetName: ''
|
|
281
|
+
};
|
|
282
|
+
for (const dataSource of getFlowDataSourceNames(ref)) {
|
|
283
|
+
connectionRefs[dataSource] = entry;
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
const scopesByApiId = Object.fromEntries(Object.entries(schemaByApiId).map(([apiId, schema]) => [apiId, {
|
|
287
|
+
will: schema.properties.scopes?.will ?? [],
|
|
288
|
+
wont: schema.properties.scopes?.wont ?? []
|
|
289
|
+
}]));
|
|
290
|
+
const requirements = buildConnectionRequirements(connectionReferences, scopesByApiId);
|
|
291
|
+
const requirementsByLocalRefId = Object.fromEntries(requirements.map(requirement => [requirement.localRefId, requirement]));
|
|
292
|
+
const missingConnectionReferences = Object.entries(resolvedIds).filter(([, connectionId]) => !connectionId).map(([localRefId]) => requirementsByLocalRefId[localRefId]).filter(requirement => Boolean(requirement));
|
|
293
|
+
const connectionCandidates = Object.fromEntries(connResults.map(result => [result.localRefId, result.instances]));
|
|
294
|
+
return {
|
|
295
|
+
connectionRefs,
|
|
296
|
+
dataverseOrgUrl,
|
|
297
|
+
databaseReferences: {},
|
|
298
|
+
missingConnectors: [...new Set(unresolvedApiIds)],
|
|
299
|
+
missingConnectionReferences,
|
|
300
|
+
connectionRequirements: Object.values(requirementsByLocalRefId),
|
|
301
|
+
connectionCandidates,
|
|
302
|
+
resourceConsents: [],
|
|
303
|
+
configurationErrors: findConnectionDataSourceConflicts(requirements).map(dataSource => `Data source "${dataSource}" is assigned to multiple connection references.`),
|
|
304
|
+
registeredConnectionUsages: [],
|
|
305
|
+
requiresConnectionConsent: requirements.length > 0
|
|
306
|
+
};
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
// ---------------------------------------------------------------------------
|
|
310
|
+
// applyAclGrants
|
|
311
|
+
// ---------------------------------------------------------------------------
|
|
312
|
+
|
|
313
|
+
/**
|
|
314
|
+
* Run idempotent post-resolution delegated consent grants for the calling app.
|
|
315
|
+
*
|
|
316
|
+
* Used by the fast path (production APKs via `fetchAppLaunchData`) where the
|
|
317
|
+
* launch API has already resolved the user's connections. Grants are applied
|
|
318
|
+
* only to user-selected connections; fixed shared connections are already
|
|
319
|
+
* authorized by their owner and must not receive delegated user consent.
|
|
320
|
+
*
|
|
321
|
+
* For the slow path the grants run before disambiguation inside
|
|
322
|
+
* `resolveViaStaticRefs`, where they can still influence which connections
|
|
323
|
+
* are considered available.
|
|
324
|
+
*
|
|
325
|
+
* Returns the connection names whose ACL grants failed so callers can fail closed.
|
|
326
|
+
*/
|
|
327
|
+
export async function applyAclGrants(connectionRefs, connectionRequirements, environmentId, region, acquireToken) {
|
|
328
|
+
const connectionNames = getConnectionNamesRequiringAclGrant(connectionRefs, connectionRequirements);
|
|
329
|
+
if (connectionNames.length === 0) return new Set();
|
|
330
|
+
const connectionNamesSet = new Set(connectionNames);
|
|
331
|
+
const consentToken = await acquireToken([connectionUserConsentScope(region)]);
|
|
332
|
+
if (!consentToken) return new Set(connectionNames);
|
|
333
|
+
const seen = new Set();
|
|
334
|
+
const failed = new Set();
|
|
335
|
+
await Promise.all(Object.values(connectionRefs).map(async ref => {
|
|
336
|
+
if (!ref.connectionName || !connectionNamesSet.has(ref.connectionName) || seen.has(ref.connectionName)) return;
|
|
337
|
+
seen.add(ref.connectionName);
|
|
338
|
+
const granted = await grantConnectionUserConsent(ref.apiId, ref.connectionName, environmentId, region, consentToken);
|
|
339
|
+
if (!granted) failed.add(ref.connectionName);
|
|
340
|
+
}));
|
|
341
|
+
return failed;
|
|
342
|
+
}
|