@microsoft/power-apps-native-host 0.2.25 → 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 +21 -2
- package/compatibility/current.json +0 -1
- package/lib/NOTICE.txt +6 -12
- package/lib/commonjs/auth/AuthContext.js +74 -18
- package/lib/commonjs/bridge/NativePowerAppsBridge.js +11 -18
- package/lib/commonjs/bridge/PowerPagesBridge.js +27 -8
- 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 +166 -4
- package/lib/commonjs/logger.js +9 -6
- package/lib/commonjs/offline/createOfflineHostActivationCallbacks.js +61 -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/PowerAppsProvider.js +173 -134
- 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/bridge/NativePowerAppsBridge.js +12 -19
- package/lib/module/bridge/PowerPagesBridge.js +27 -8
- 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 +10 -4
- package/lib/module/logger.js +8 -6
- package/lib/module/offline/createOfflineHostActivationCallbacks.js +55 -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/PowerAppsProvider.js +177 -138
- 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 +1 -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 +17 -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/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/PowerAppsProvider.d.ts +30 -8
- 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 +1 -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 +17 -10
- package/lib/typescript/module/logger.d.ts +4 -4
- package/lib/typescript/module/offline/createOfflineHostActivationCallbacks.d.ts +12 -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/PowerAppsProvider.d.ts +30 -8
- 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 +11 -6
- package/scripts/metro-logger.js +213 -0
- package/scripts/upgrade/runtime.js +13 -7
- package/scripts/upgrade-authoring.js +5 -6
- 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
|
@@ -3,14 +3,7 @@
|
|
|
3
3
|
/*!
|
|
4
4
|
* power-apps-native-host — PowerPlatformEnvironmentApi
|
|
5
5
|
*
|
|
6
|
-
*
|
|
7
|
-
* pattern: resolves the Power Platform environment-scoped API endpoint from the
|
|
8
|
-
* environment GUID and region, then fetches linkedEnvironmentMetadata to obtain
|
|
9
|
-
* the Dataverse instanceUrl.
|
|
10
|
-
*
|
|
11
|
-
* Reference (Host SDK):
|
|
12
|
-
* packages/power-apps-host/src/providers/defaults/DefaultAppMetadataProvider.ts
|
|
13
|
-
* packages/power-apps-common/src/services/PowerPlatformApiDiscovery.ts
|
|
6
|
+
* Resolves Power Platform RP endpoints and scopes.
|
|
14
7
|
*/
|
|
15
8
|
|
|
16
9
|
// ---------------------------------------------------------------------------
|
|
@@ -69,10 +62,6 @@ export function buildEnvironmentEndpoint(environmentId, region) {
|
|
|
69
62
|
return `https://${hexPrefix}.${hexSuffix}.environment.${hostNameSuffix}`;
|
|
70
63
|
}
|
|
71
64
|
|
|
72
|
-
// ---------------------------------------------------------------------------
|
|
73
|
-
// Public API
|
|
74
|
-
// ---------------------------------------------------------------------------
|
|
75
|
-
|
|
76
65
|
/**
|
|
77
66
|
* Token scope for the Power Platform management API (prod cluster).
|
|
78
67
|
* Non-prod clusters (test, gov, high, dev, mooncake, etc.) host the management
|
|
@@ -90,97 +79,9 @@ export function powerPlatformScope(region) {
|
|
|
90
79
|
const host = HOST_NAME_SUFFIX[region] ?? HOST_NAME_SUFFIX['prod'];
|
|
91
80
|
return `https://${host}/.default`;
|
|
92
81
|
}
|
|
93
|
-
import { getAppLogger, HostOperationNames } from "../telemetry/index.js";
|
|
94
|
-
function getSafeErrorName(error) {
|
|
95
|
-
return error instanceof Error ? error.constructor.name : typeof error;
|
|
96
|
-
}
|
|
97
82
|
|
|
98
|
-
/**
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
* `databaseReferences[*].databaseDetails.linkedEnvironmentMetadata`.
|
|
103
|
-
*
|
|
104
|
-
* @param environmentId - Environment GUID (from power.config.json `environmentId`).
|
|
105
|
-
* @param region - Cluster region string (from power.config.json `region`, default "prod").
|
|
106
|
-
* @param acquireToken - Callback to obtain a bearer token for the given scopes.
|
|
107
|
-
* Scope will be {@link powerPlatformScope}`(region)`.
|
|
108
|
-
* @param bearerToken - Optional pre-fetched bearer token for {@link powerPlatformScope}`(region)`.
|
|
109
|
-
* Pass this to avoid a redundant `acquireToken` round-trip when the
|
|
110
|
-
* caller has already obtained a token for the same audience.
|
|
111
|
-
* @returns The Dataverse org URL (e.g. "https://org12345.crm.dynamics.com"), or
|
|
112
|
-
* `undefined` if the environment has no linked CDS database.
|
|
113
|
-
*/
|
|
114
|
-
export async function fetchInstanceUrl(environmentId, region, acquireToken, bearerToken) {
|
|
115
|
-
const scenario = getAppLogger().trackScenario(HostOperationNames.PlatformFetchInstanceUrl, {
|
|
116
|
-
environmentId,
|
|
117
|
-
region
|
|
118
|
-
});
|
|
119
|
-
try {
|
|
120
|
-
const baseUrl = buildEnvironmentEndpoint(environmentId, region);
|
|
121
|
-
const url = `${baseUrl}/powerapps/environment?api-version=1&%24filter=name+eq+%27${encodeURIComponent(environmentId)}%27`;
|
|
122
|
-
const token = bearerToken ?? (await acquireToken([powerPlatformScope(region)]));
|
|
123
|
-
if (!token) {
|
|
124
|
-
throw new Error('fetchInstanceUrl: failed to acquire Power Platform API token');
|
|
125
|
-
}
|
|
126
|
-
const response = await fetch(url, {
|
|
127
|
-
method: 'GET',
|
|
128
|
-
headers: {
|
|
129
|
-
Authorization: `Bearer ${token}`,
|
|
130
|
-
'Content-Type': 'application/json'
|
|
131
|
-
}
|
|
132
|
-
});
|
|
133
|
-
if (!response.ok) {
|
|
134
|
-
throw new Error(`fetchInstanceUrl: environment API returned ${response.status} ${response.statusText}`);
|
|
135
|
-
}
|
|
136
|
-
const env = await response.json();
|
|
137
|
-
const instanceUrl = env.properties?.linkedEnvironmentMetadata?.instanceUrl;
|
|
138
|
-
scenario.complete({
|
|
139
|
-
hasInstanceUrl: instanceUrl !== undefined
|
|
140
|
-
});
|
|
141
|
-
return instanceUrl;
|
|
142
|
-
} catch (e) {
|
|
143
|
-
scenario.fail(undefined, {
|
|
144
|
-
errorName: getSafeErrorName(e)
|
|
145
|
-
});
|
|
146
|
-
throw e;
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
// ---------------------------------------------------------------------------
|
|
151
|
-
// buildCdsDataSources
|
|
152
|
-
// ---------------------------------------------------------------------------
|
|
153
|
-
|
|
154
|
-
/**
|
|
155
|
-
* Shape of a single Dataverse table entry inside power.config.json
|
|
156
|
-
* `databaseReferences[*].dataSources[*]`.
|
|
157
|
-
*/
|
|
158
|
-
|
|
159
|
-
/**
|
|
160
|
-
* Shape of a single database reference entry inside power.config.json
|
|
161
|
-
* `databaseReferences`.
|
|
162
|
-
*/
|
|
163
|
-
|
|
164
|
-
/**
|
|
165
|
-
* Build the CDS data source config array that the SDK reads via
|
|
166
|
-
* AppPowerAppsClientPlugin.getAppCdsDataSourceConfigsAsync.
|
|
167
|
-
*
|
|
168
|
-
* Pass the resolved Dataverse org URL (from useConnectionRefs) and the
|
|
169
|
-
* databaseReferences map from power.config.json. The resulting array is
|
|
170
|
-
* passed directly to <PowerAppsHostProvider dataSources={...} />.
|
|
171
|
-
*
|
|
172
|
-
* @param databaseReferences - The `databaseReferences` map from power.config.json.
|
|
173
|
-
* @param dataverseOrgUrl - The resolved Dataverse org URL
|
|
174
|
-
* (connectionRefs['commondataserviceforapps']?.datasetName).
|
|
175
|
-
* Pass an empty string before the org URL is known;
|
|
176
|
-
* the host provider will receive empty runtimeUrl entries
|
|
177
|
-
* and the SDK will not attempt Dataverse calls.
|
|
178
|
-
*/
|
|
179
|
-
export function buildCdsDataSources(databaseReferences, dataverseOrgUrl) {
|
|
180
|
-
return Object.values(databaseReferences).flatMap(dbRef => Object.values(dbRef.dataSources ?? {}).map(ds => ({
|
|
181
|
-
runtimeUrl: dataverseOrgUrl ? `${dataverseOrgUrl}/api/data/v9.0/` : '',
|
|
182
|
-
entitySetName: ds.entitySetName ?? '',
|
|
183
|
-
logicalName: ds.logicalName ?? '',
|
|
184
|
-
version: '9.2'
|
|
185
|
-
})));
|
|
83
|
+
/** Delegated permission required by the connection user-consent endpoint. */
|
|
84
|
+
export function connectionUserConsentScope(region) {
|
|
85
|
+
const host = HOST_NAME_SUFFIX[region] ?? HOST_NAME_SUFFIX['prod'];
|
|
86
|
+
return `https://${host}/Connectivity.Connections.UserConsent`;
|
|
186
87
|
}
|
|
@@ -30,20 +30,26 @@
|
|
|
30
30
|
*/
|
|
31
31
|
|
|
32
32
|
import React, { useEffect, useLayoutEffect, useRef, useState, useCallback, useMemo } from 'react';
|
|
33
|
-
import { ActivityIndicator, View } from 'react-native';
|
|
33
|
+
import { ActivityIndicator, Text, TouchableOpacity, View } from 'react-native';
|
|
34
34
|
import { SafeAreaProvider } from 'react-native-safe-area-context';
|
|
35
35
|
import { setupNativeHost } from "../bridge/setupNativeHost.js";
|
|
36
36
|
import { buildConnectionReferences } from "../bridge/NativePowerAppsBridge.js";
|
|
37
|
-
import { useConnectionRefs } from "../
|
|
38
|
-
import { buildCdsDataSources } from "../platform/
|
|
37
|
+
import { useConnectionRefs } from "../connections/useConnectionRefs.js";
|
|
38
|
+
import { buildCdsDataSources } from "../platform/DataverseDataSources.js";
|
|
39
|
+
import { powerPlatformScope } from "../platform/PowerPlatformEnvironmentApi.js";
|
|
39
40
|
import { ConnectionSetupScreen } from "../connections/ConnectionSetupScreen.js";
|
|
41
|
+
import { buildConnectionResolutionKey } from "../connections/types.js";
|
|
42
|
+
import { postConnectionUsage, reportScopeConsents } from "../connections/PowerPlatformConnectionApi.js";
|
|
43
|
+
import { shouldPersistConnectionUsages, shouldShowConnectionReview } from "../connections/connectionState.js";
|
|
44
|
+
import { isDevPlayer } from "../utils/isDevPlayer.js";
|
|
40
45
|
import { debugLog } from "../logger.js";
|
|
41
46
|
import { getAppLogger, HostOperationNames } from "../telemetry/index.js";
|
|
42
47
|
|
|
43
48
|
// Entra ID token scope for the APIM connector proxy (API Hub).
|
|
44
|
-
import { jsx as _jsx, Fragment as _Fragment } from "react/jsx-runtime";
|
|
49
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
45
50
|
const CONNECTOR_SCOPE = 'https://apihub.azure.com/.default';
|
|
46
51
|
const EMPTY_DATABASE_REFERENCES = {};
|
|
52
|
+
const EMPTY_CONNECTION_REFERENCES = {};
|
|
47
53
|
/**
|
|
48
54
|
* Initialises the NativePowerAppsBridge and manages the full pre-launch
|
|
49
55
|
* pipeline — connection resolution, disambiguation, and creation — before
|
|
@@ -54,6 +60,7 @@ const EMPTY_DATABASE_REFERENCES = {};
|
|
|
54
60
|
export function PowerAppsHostProvider({
|
|
55
61
|
acquireToken,
|
|
56
62
|
isSignedIn,
|
|
63
|
+
onSignOut,
|
|
57
64
|
appId,
|
|
58
65
|
environmentId,
|
|
59
66
|
region = 'prod',
|
|
@@ -63,18 +70,75 @@ export function PowerAppsHostProvider({
|
|
|
63
70
|
renderOAuthWebView,
|
|
64
71
|
isvClientId,
|
|
65
72
|
onDisambiguationNeeded,
|
|
73
|
+
onConnectionConsent,
|
|
74
|
+
onResourceConsent,
|
|
75
|
+
onConnectionConsentDeclined,
|
|
66
76
|
children
|
|
67
77
|
}) {
|
|
68
78
|
// Extra refs produced by ConnectionSetupScreen for newly-created connections.
|
|
69
79
|
const [extraConnectionRefs, setExtraConnectionRefs] = useState({});
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
const
|
|
80
|
+
const [consentApproved, setConsentApproved] = useState(false);
|
|
81
|
+
const setupDone = consentApproved;
|
|
82
|
+
const renderConnectionError = (title, messages, useNeutralText = false) => /*#__PURE__*/_jsxs(View, {
|
|
83
|
+
style: {
|
|
84
|
+
flex: 1,
|
|
85
|
+
padding: 24,
|
|
86
|
+
justifyContent: 'center',
|
|
87
|
+
backgroundColor: '#F8F8F8'
|
|
88
|
+
},
|
|
89
|
+
children: [/*#__PURE__*/_jsx(Text, {
|
|
90
|
+
style: {
|
|
91
|
+
fontSize: 20,
|
|
92
|
+
fontWeight: '700',
|
|
93
|
+
color: '#1A1A1A',
|
|
94
|
+
marginBottom: 12
|
|
95
|
+
},
|
|
96
|
+
children: title
|
|
97
|
+
}), messages.map(message => /*#__PURE__*/_jsx(Text, {
|
|
98
|
+
style: {
|
|
99
|
+
fontSize: 14,
|
|
100
|
+
color: useNeutralText ? '#323130' : '#C4262E',
|
|
101
|
+
lineHeight: 20,
|
|
102
|
+
marginBottom: 8
|
|
103
|
+
},
|
|
104
|
+
children: message
|
|
105
|
+
}, message)), isSignedIn && onSignOut ? /*#__PURE__*/_jsx(TouchableOpacity, {
|
|
106
|
+
accessibilityRole: "button",
|
|
107
|
+
onPress: () => {
|
|
108
|
+
void onSignOut();
|
|
109
|
+
},
|
|
110
|
+
style: {
|
|
111
|
+
alignSelf: 'flex-start',
|
|
112
|
+
backgroundColor: '#0F6CBD',
|
|
113
|
+
paddingHorizontal: 20,
|
|
114
|
+
paddingVertical: 12,
|
|
115
|
+
borderRadius: 4,
|
|
116
|
+
marginTop: 12
|
|
117
|
+
},
|
|
118
|
+
children: /*#__PURE__*/_jsx(Text, {
|
|
119
|
+
style: {
|
|
120
|
+
color: '#FFFFFF',
|
|
121
|
+
fontSize: 15,
|
|
122
|
+
fontWeight: '600'
|
|
123
|
+
},
|
|
124
|
+
children: "Sign out"
|
|
125
|
+
})
|
|
126
|
+
}) : null]
|
|
127
|
+
});
|
|
73
128
|
const {
|
|
74
129
|
connectionRefs,
|
|
75
130
|
isReady,
|
|
131
|
+
error: connectionError,
|
|
132
|
+
errorKind: connectionErrorKind,
|
|
76
133
|
missingConnectors,
|
|
77
|
-
|
|
134
|
+
missingConnectionReferences,
|
|
135
|
+
connectionRequirements,
|
|
136
|
+
resourceConsents: launchResourceConsents,
|
|
137
|
+
configurationErrors,
|
|
138
|
+
requiresConnectionConsent,
|
|
139
|
+
resolvedConfigKey,
|
|
140
|
+
dataverseOrgUrl,
|
|
141
|
+
databaseReferences: resolvedDatabaseReferences
|
|
78
142
|
} = useConnectionRefs({
|
|
79
143
|
isSignedIn,
|
|
80
144
|
acquireToken,
|
|
@@ -92,39 +156,43 @@ export function PowerAppsHostProvider({
|
|
|
92
156
|
if (!isSignedIn) {
|
|
93
157
|
debugLog('[PAHost] user signed out — resetting bridge state');
|
|
94
158
|
setExtraConnectionRefs({});
|
|
159
|
+
setConsentApproved(false);
|
|
95
160
|
}
|
|
96
161
|
}, [isSignedIn]);
|
|
162
|
+
useEffect(() => {
|
|
163
|
+
setExtraConnectionRefs({});
|
|
164
|
+
setConsentApproved(false);
|
|
165
|
+
}, [appId, environmentId, connectionReferences]);
|
|
97
166
|
|
|
98
167
|
// Merge hook refs with anything the setup screen created.
|
|
99
168
|
const mergedRefs = useMemo(() => ({
|
|
100
169
|
...connectionRefs,
|
|
101
170
|
...extraConnectionRefs
|
|
102
171
|
}), [connectionRefs, extraConnectionRefs]);
|
|
103
|
-
const staticConnectionRefs = useMemo(() => buildConnectionReferences(connectionReferences), [connectionReferences]);
|
|
172
|
+
const staticConnectionRefs = useMemo(() => isDevPlayer ? buildConnectionReferences(connectionReferences) : EMPTY_CONNECTION_REFERENCES, [connectionReferences, isDevPlayer]);
|
|
104
173
|
|
|
105
174
|
// dataverseOrgUrl comes directly from useConnectionRefs (fetched from the
|
|
106
175
|
// environment metadata regardless of whether a commondataserviceforapps
|
|
107
176
|
// connection reference is configured). This allows Dataverse calls to work
|
|
108
177
|
// even when there is no explicit Dataverse connection in connectionReferences.
|
|
109
|
-
const cdsDataSources = useMemo(() => buildCdsDataSources(databaseReferences ?? EMPTY_DATABASE_REFERENCES, dataverseOrgUrl), [databaseReferences, dataverseOrgUrl]);
|
|
178
|
+
const cdsDataSources = useMemo(() => buildCdsDataSources(isDevPlayer ? databaseReferences ?? EMPTY_DATABASE_REFERENCES : resolvedDatabaseReferences, dataverseOrgUrl), [databaseReferences, resolvedDatabaseReferences, dataverseOrgUrl, isDevPlayer]);
|
|
110
179
|
|
|
111
180
|
// Build the missing-connector entries for ConnectionSetupScreen.
|
|
112
|
-
const missingEntries = useMemo(() =>
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
};
|
|
118
|
-
}), [missingConnectors, connectionReferences]);
|
|
181
|
+
const missingEntries = useMemo(() => missingConnectionReferences, [missingConnectionReferences]);
|
|
182
|
+
const resourceConsents = useMemo(() => [...new Map(launchResourceConsents.map(resource => [resource.resourceId.toLowerCase(), resource])).values()], [launchResourceConsents]);
|
|
183
|
+
const currentConfigKey = useMemo(() => buildConnectionResolutionKey(appId, environmentId, isDevPlayer ? connectionReferences : {}), [appId, environmentId, connectionReferences, isDevPlayer]);
|
|
184
|
+
const resolutionMatchesCurrentConfig = resolvedConfigKey === currentConfigKey;
|
|
185
|
+
const needsConsent = shouldShowConnectionReview(isDevPlayer, requiresConnectionConsent, resourceConsents.length > 0);
|
|
119
186
|
|
|
120
187
|
// Called by ConnectionSetupScreen once every missing connector is resolved.
|
|
121
|
-
const handleSetupComplete = useCallback(resolvedIds => {
|
|
188
|
+
const handleSetupComplete = useCallback(async resolvedIds => {
|
|
122
189
|
const refs = {};
|
|
123
|
-
for (const [
|
|
124
|
-
const
|
|
125
|
-
if (!
|
|
126
|
-
const
|
|
127
|
-
const
|
|
190
|
+
for (const [localRefId, connectionId] of Object.entries(resolvedIds)) {
|
|
191
|
+
const requirement = connectionRequirements.find(item => item.localRefId === localRefId);
|
|
192
|
+
if (!requirement) continue;
|
|
193
|
+
const apiId = requirement.apiId;
|
|
194
|
+
const dataSources = requirement.dataSources;
|
|
195
|
+
const runtimeUrl = dataSources.map(ds => connectionRefsRef.current[ds]?.runtimeUrl ?? (isDevPlayer ? schemaMap[ds]?.properties?.primaryRuntimeUrl : undefined)).find(Boolean) ?? '';
|
|
128
196
|
for (const ds of dataSources) {
|
|
129
197
|
refs[ds] = {
|
|
130
198
|
apiId,
|
|
@@ -138,7 +206,44 @@ export function PowerAppsHostProvider({
|
|
|
138
206
|
}
|
|
139
207
|
}
|
|
140
208
|
setExtraConnectionRefs(refs);
|
|
141
|
-
|
|
209
|
+
const mergedForConsent = {
|
|
210
|
+
...connectionRefsRef.current,
|
|
211
|
+
...refs
|
|
212
|
+
};
|
|
213
|
+
const usages = connectionRequirements.map(requirement => ({
|
|
214
|
+
localRefId: requirement.localRefId,
|
|
215
|
+
apiId: requirement.apiId,
|
|
216
|
+
connectionId: requirement.dataSources.map(dataSource => mergedForConsent[dataSource]?.connectionName).find(Boolean) ?? '',
|
|
217
|
+
dataSources: requirement.dataSources,
|
|
218
|
+
appActions: requirement.appActions ?? [],
|
|
219
|
+
apiScopes: requirement.apiScopes
|
|
220
|
+
}));
|
|
221
|
+
if (usages.some(usage => !usage.connectionId)) {
|
|
222
|
+
throw new Error('Not all required connections have been resolved.');
|
|
223
|
+
}
|
|
224
|
+
if (!isDevPlayer && resourceConsents.length > 0 && !onResourceConsent) {
|
|
225
|
+
throw new Error('Protected resource consent requires an onResourceConsent persistence callback.');
|
|
226
|
+
}
|
|
227
|
+
const shouldPersistUsages = shouldPersistConnectionUsages(isDevPlayer, appId);
|
|
228
|
+
if (shouldPersistUsages && appId && usages.length > 0) {
|
|
229
|
+
const token = await acquireToken([powerPlatformScope(region)]);
|
|
230
|
+
if (!token) throw new Error('Unable to acquire a token to register connection usage.');
|
|
231
|
+
await Promise.all(usages.map(usage => postConnectionUsage(appId, usage, environmentId, region, token)));
|
|
232
|
+
await reportScopeConsents(appId, usages, environmentId, region, token);
|
|
233
|
+
}
|
|
234
|
+
if (!isDevPlayer) {
|
|
235
|
+
await onConnectionConsent?.(usages);
|
|
236
|
+
await onResourceConsent?.(resourceConsents);
|
|
237
|
+
}
|
|
238
|
+
setConsentApproved(true);
|
|
239
|
+
}, [acquireToken, appId, environmentId, region, schemaMap, connectionRequirements, onConnectionConsent, onResourceConsent, resourceConsents, isDevPlayer]);
|
|
240
|
+
const consentDetails = useMemo(() => connectionRequirements.map(requirement => ({
|
|
241
|
+
localRefId: requirement.localRefId,
|
|
242
|
+
connectorId: requirement.connectorId,
|
|
243
|
+
displayName: requirement.displayName ?? requirement.connectorId.replace(/^shared_/, ''),
|
|
244
|
+
description: `Uses ${requirement.dataSources.join(', ')}`,
|
|
245
|
+
appActions: requirement.appActions ?? []
|
|
246
|
+
})), [connectionRequirements]);
|
|
142
247
|
|
|
143
248
|
// ---- Bridge wiring ----
|
|
144
249
|
|
|
@@ -150,7 +255,7 @@ export function PowerAppsHostProvider({
|
|
|
150
255
|
connectionRefsRef.current = connectionRefs;
|
|
151
256
|
const configuredBridgeInputsRef = useRef(undefined);
|
|
152
257
|
const [, setBridgeRegistrationVersion] = useState(0);
|
|
153
|
-
const hasResolvedConnections = isSignedIn && isReady && (missingConnectors.length === 0 ||
|
|
258
|
+
const hasResolvedConnections = isSignedIn && isReady && resolutionMatchesCurrentConfig && (missingConnectors.length === 0 || consentApproved) && (!needsConsent || consentApproved);
|
|
154
259
|
const isBridgeConfiguredFor = (connections, dataSources) => {
|
|
155
260
|
const currentInputs = configuredBridgeInputsRef.current;
|
|
156
261
|
return currentInputs?.connections === connections && currentInputs.dataSources === dataSources;
|
|
@@ -250,13 +355,34 @@ export function PowerAppsHostProvider({
|
|
|
250
355
|
}
|
|
251
356
|
}, [isSignedIn, isReady]);
|
|
252
357
|
useEffect(() => {
|
|
253
|
-
if (isSignedIn && isReady && missingConnectors.length > 0 && !setupDone) {
|
|
358
|
+
if (isSignedIn && isReady && (missingConnectors.length > 0 || needsConsent) && !setupDone) {
|
|
254
359
|
debugLog(`[PAHost] render: showing ConnectionSetupScreen for ${missingConnectors.length} missing connector(s):`, missingConnectors.join(', '));
|
|
255
360
|
}
|
|
256
|
-
}, [isSignedIn, isReady, missingConnectors, setupDone]);
|
|
361
|
+
}, [isSignedIn, isReady, missingConnectors, needsConsent, setupDone]);
|
|
257
362
|
|
|
258
363
|
// ---- Render ----
|
|
259
364
|
|
|
365
|
+
if (connectionError) {
|
|
366
|
+
const accessDenied = connectionErrorKind === 'app-access-denied';
|
|
367
|
+
return renderConnectionError(accessDenied ? 'You don’t have access' : 'Unable to load app', [connectionError], accessDenied);
|
|
368
|
+
}
|
|
369
|
+
if (configurationErrors.length > 0) {
|
|
370
|
+
return renderConnectionError('Connection configuration error', configurationErrors);
|
|
371
|
+
}
|
|
372
|
+
if (isSignedIn && !resolutionMatchesCurrentConfig) {
|
|
373
|
+
return /*#__PURE__*/_jsx(View, {
|
|
374
|
+
style: {
|
|
375
|
+
flex: 1,
|
|
376
|
+
alignItems: 'center',
|
|
377
|
+
justifyContent: 'center',
|
|
378
|
+
backgroundColor: '#F8F8F8'
|
|
379
|
+
},
|
|
380
|
+
children: /*#__PURE__*/_jsx(ActivityIndicator, {
|
|
381
|
+
size: "large"
|
|
382
|
+
})
|
|
383
|
+
});
|
|
384
|
+
}
|
|
385
|
+
|
|
260
386
|
// First mount: register the native bridge with static manifest refs before
|
|
261
387
|
// any route children can mount and call into @microsoft/power-apps.
|
|
262
388
|
if (!staticBridgeRegistered && !hasResolvedConnections) {
|
|
@@ -292,7 +418,7 @@ export function PowerAppsHostProvider({
|
|
|
292
418
|
// Wrapped in SafeAreaProvider so useSafeAreaInsets() works even when the
|
|
293
419
|
// consuming app hasn't mounted its own SafeAreaProvider yet. Nesting is
|
|
294
420
|
// safe — react-native-safe-area-context treats inner providers as no-ops.
|
|
295
|
-
if (isSignedIn && isReady && missingConnectors.length > 0 && !setupDone) {
|
|
421
|
+
if (isSignedIn && isReady && (missingConnectors.length > 0 || needsConsent) && !setupDone) {
|
|
296
422
|
return /*#__PURE__*/_jsx(SafeAreaProvider, {
|
|
297
423
|
children: /*#__PURE__*/_jsx(ConnectionSetupScreen, {
|
|
298
424
|
missingConnectors: missingEntries,
|
|
@@ -300,8 +426,13 @@ export function PowerAppsHostProvider({
|
|
|
300
426
|
environmentId: environmentId,
|
|
301
427
|
region: region,
|
|
302
428
|
acquireToken: acquireToken,
|
|
429
|
+
onSignOut: isSignedIn ? onSignOut : undefined,
|
|
303
430
|
isvClientId: isvClientId,
|
|
304
431
|
onAllConnected: handleSetupComplete,
|
|
432
|
+
consentDetails: consentDetails,
|
|
433
|
+
resourceConsents: resourceConsents,
|
|
434
|
+
onConsentDeclined: onConnectionConsentDeclined,
|
|
435
|
+
showReview: !isDevPlayer,
|
|
305
436
|
renderOAuthWebView: renderOAuthWebView
|
|
306
437
|
// No onSkip — connections are required.
|
|
307
438
|
})
|