@microsoft/power-apps-native-host 0.2.25 → 0.3.3
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 -10
- package/compatibility/current.json +31 -4
- package/lib/NOTICE.txt +1325 -24
- package/lib/commonjs/auth/AuthContext.js +110 -33
- 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/config/babelConfig.js +13 -0
- package/lib/commonjs/config/expoConfig.js +71 -0
- package/lib/commonjs/config/metroConfig.js +70 -0
- package/lib/commonjs/config/metroLogger.js +182 -0
- package/lib/commonjs/config/tamaguiConfig.js +98 -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 +348 -0
- package/lib/commonjs/connections/types.js +106 -0
- package/lib/commonjs/{platform → connections}/useConnectionRefs.js +130 -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 +167 -4
- package/lib/commonjs/logger.js +9 -6
- package/lib/commonjs/offline/createOfflineHostActivationCallbacks.js +61 -0
- package/lib/commonjs/offline/useOfflineIntegrationWithStartupGate.js +48 -0
- package/lib/commonjs/offline/useOfflineIntegrationWithStartupGate.web.js +13 -0
- package/lib/commonjs/platform/DataverseDataSources.js +18 -0
- package/lib/commonjs/platform/DataverseGlobalDiscoveryApi.js +94 -0
- package/lib/commonjs/platform/GlobalDiscovery.js +51 -0
- package/lib/commonjs/platform/PowerPlatformEnvironmentApi.js +6 -106
- package/lib/commonjs/provider/PowerAppsHostProvider.js +157 -25
- package/lib/commonjs/provider/PowerAppsProvider.js +178 -134
- package/lib/commonjs/provider/SessionDetailsGesture.js +208 -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 +110 -33
- 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/config/babelConfig.js +9 -0
- package/lib/module/config/expoConfig.js +67 -0
- package/lib/module/config/metroConfig.js +65 -0
- package/lib/module/config/metroLogger.js +175 -0
- package/lib/module/config/tamaguiConfig.js +93 -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 +343 -0
- package/lib/module/connections/types.js +91 -0
- package/lib/module/{platform → connections}/useConnectionRefs.js +131 -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 -4
- package/lib/module/logger.js +8 -6
- package/lib/module/offline/createOfflineHostActivationCallbacks.js +55 -0
- package/lib/module/offline/useOfflineIntegrationWithStartupGate.js +44 -0
- package/lib/module/offline/useOfflineIntegrationWithStartupGate.web.js +9 -0
- package/lib/module/platform/DataverseDataSources.js +13 -0
- package/lib/module/platform/DataverseGlobalDiscoveryApi.js +89 -0
- package/lib/module/platform/GlobalDiscovery.js +45 -0
- package/lib/module/platform/PowerPlatformEnvironmentApi.js +5 -104
- package/lib/module/provider/PowerAppsHostProvider.js +160 -28
- package/lib/module/provider/PowerAppsProvider.js +182 -138
- package/lib/module/provider/SessionDetailsGesture.js +203 -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/config/babelConfig.d.ts +12 -0
- package/lib/typescript/commonjs/config/expoConfig.d.ts +21 -0
- package/lib/typescript/commonjs/config/metroConfig.d.ts +6 -0
- package/lib/typescript/commonjs/config/metroLogger.d.ts +21 -0
- package/lib/typescript/commonjs/config/tamaguiConfig.d.ts +1792 -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 +22 -20
- package/lib/typescript/commonjs/connections/types.d.ts +129 -1
- package/lib/typescript/commonjs/{platform → connections}/useConnectionRefs.d.ts +36 -25
- 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 +18 -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 +28 -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 +54 -9
- 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/config/babelConfig.d.ts +12 -0
- package/lib/typescript/module/config/expoConfig.d.ts +21 -0
- package/lib/typescript/module/config/metroConfig.d.ts +6 -0
- package/lib/typescript/module/config/metroLogger.d.ts +21 -0
- package/lib/typescript/module/config/tamaguiConfig.d.ts +1792 -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 +22 -20
- package/lib/typescript/module/connections/types.d.ts +129 -1
- package/lib/typescript/module/{platform → connections}/useConnectionRefs.d.ts +36 -25
- 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 +18 -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 +28 -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 +54 -9
- 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 +52 -13
- package/scripts/build-codegen-package.js +283 -0
- package/scripts/start-web.mjs +9 -5
- package/scripts/upgrade/runtime.js +139 -24
- package/scripts/upgrade-authoring.js +274 -180
- package/scripts/upgrade.js +10 -16
- package/src/config/generated-config.d.ts +4 -0
- package/src/config/index.d.ts +3 -0
- package/src/config/tsconfig.json +26 -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,45 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
const GLOBAL_DISCOVERY_HOST = {
|
|
4
|
+
prod: 'globaldisco.crm.dynamics.com',
|
|
5
|
+
gcc: 'globaldisco.crm9.dynamics.com',
|
|
6
|
+
usg: 'globaldisco.crm.microsoftdynamics.us',
|
|
7
|
+
dod: 'globaldisco.crm.appsplatform.us',
|
|
8
|
+
china: 'globaldisco.crm.dynamics.cn'
|
|
9
|
+
};
|
|
10
|
+
const REGION_DISCOVERY_CLOUD = {
|
|
11
|
+
dev: 'prod',
|
|
12
|
+
test: 'prod',
|
|
13
|
+
preprod: 'prod',
|
|
14
|
+
preview: 'prod',
|
|
15
|
+
firstrelease: 'prod',
|
|
16
|
+
prod: 'prod',
|
|
17
|
+
gccmoderate: 'gcc',
|
|
18
|
+
gov: 'gcc',
|
|
19
|
+
gcchigh: 'usg',
|
|
20
|
+
high: 'usg',
|
|
21
|
+
dod: 'dod',
|
|
22
|
+
mooncake: 'china'
|
|
23
|
+
};
|
|
24
|
+
const ENVIRONMENT_ID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
25
|
+
export function globalDiscoveryEndpoint(region) {
|
|
26
|
+
const cloud = REGION_DISCOVERY_CLOUD[region];
|
|
27
|
+
if (!cloud) {
|
|
28
|
+
throw new Error(`Dataverse Global Discovery is not available for Power Platform region "${region}"`);
|
|
29
|
+
}
|
|
30
|
+
return `https://${GLOBAL_DISCOVERY_HOST[cloud]}`;
|
|
31
|
+
}
|
|
32
|
+
export function globalDiscoveryScope(region) {
|
|
33
|
+
return `${globalDiscoveryEndpoint(region)}/user_impersonation`;
|
|
34
|
+
}
|
|
35
|
+
export function buildGlobalDiscoveryUrl(environmentId, region) {
|
|
36
|
+
if (!ENVIRONMENT_ID_PATTERN.test(environmentId)) {
|
|
37
|
+
throw new Error(`Invalid Power Platform environment ID: "${environmentId}"`);
|
|
38
|
+
}
|
|
39
|
+
const normalizedEnvironmentId = environmentId.toLowerCase();
|
|
40
|
+
const query = new URLSearchParams({
|
|
41
|
+
'$select': 'ApiUrl,EnvironmentId',
|
|
42
|
+
'$filter': `EnvironmentId eq '${normalizedEnvironmentId}'`
|
|
43
|
+
});
|
|
44
|
+
return `${globalDiscoveryEndpoint(region)}/api/discovery/v2.0/Instances?${query.toString()}`;
|
|
45
|
+
}
|
|
@@ -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, hasDataverseDatabaseReferences } 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,
|
|
@@ -82,6 +146,7 @@ export function PowerAppsHostProvider({
|
|
|
82
146
|
environmentId,
|
|
83
147
|
region,
|
|
84
148
|
connectionReferences,
|
|
149
|
+
hasDataverse: hasDataverseDatabaseReferences(databaseReferences),
|
|
85
150
|
schemaMap,
|
|
86
151
|
onDisambiguationNeeded,
|
|
87
152
|
isvClientId
|
|
@@ -92,39 +157,43 @@ export function PowerAppsHostProvider({
|
|
|
92
157
|
if (!isSignedIn) {
|
|
93
158
|
debugLog('[PAHost] user signed out — resetting bridge state');
|
|
94
159
|
setExtraConnectionRefs({});
|
|
160
|
+
setConsentApproved(false);
|
|
95
161
|
}
|
|
96
162
|
}, [isSignedIn]);
|
|
163
|
+
useEffect(() => {
|
|
164
|
+
setExtraConnectionRefs({});
|
|
165
|
+
setConsentApproved(false);
|
|
166
|
+
}, [appId, environmentId, connectionReferences]);
|
|
97
167
|
|
|
98
168
|
// Merge hook refs with anything the setup screen created.
|
|
99
169
|
const mergedRefs = useMemo(() => ({
|
|
100
170
|
...connectionRefs,
|
|
101
171
|
...extraConnectionRefs
|
|
102
172
|
}), [connectionRefs, extraConnectionRefs]);
|
|
103
|
-
const staticConnectionRefs = useMemo(() => buildConnectionReferences(connectionReferences), [connectionReferences]);
|
|
173
|
+
const staticConnectionRefs = useMemo(() => isDevPlayer ? buildConnectionReferences(connectionReferences) : EMPTY_CONNECTION_REFERENCES, [connectionReferences, isDevPlayer]);
|
|
104
174
|
|
|
105
175
|
// dataverseOrgUrl comes directly from useConnectionRefs (fetched from the
|
|
106
176
|
// environment metadata regardless of whether a commondataserviceforapps
|
|
107
177
|
// connection reference is configured). This allows Dataverse calls to work
|
|
108
178
|
// even when there is no explicit Dataverse connection in connectionReferences.
|
|
109
|
-
const cdsDataSources = useMemo(() => buildCdsDataSources(databaseReferences ?? EMPTY_DATABASE_REFERENCES, dataverseOrgUrl), [databaseReferences, dataverseOrgUrl]);
|
|
179
|
+
const cdsDataSources = useMemo(() => buildCdsDataSources(isDevPlayer ? databaseReferences ?? EMPTY_DATABASE_REFERENCES : resolvedDatabaseReferences, dataverseOrgUrl), [databaseReferences, resolvedDatabaseReferences, dataverseOrgUrl, isDevPlayer]);
|
|
110
180
|
|
|
111
181
|
// Build the missing-connector entries for ConnectionSetupScreen.
|
|
112
|
-
const missingEntries = useMemo(() =>
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
};
|
|
118
|
-
}), [missingConnectors, connectionReferences]);
|
|
182
|
+
const missingEntries = useMemo(() => missingConnectionReferences, [missingConnectionReferences]);
|
|
183
|
+
const resourceConsents = useMemo(() => [...new Map(launchResourceConsents.map(resource => [resource.resourceId.toLowerCase(), resource])).values()], [launchResourceConsents]);
|
|
184
|
+
const currentConfigKey = useMemo(() => buildConnectionResolutionKey(appId, environmentId, isDevPlayer ? connectionReferences : {}), [appId, environmentId, connectionReferences, isDevPlayer]);
|
|
185
|
+
const resolutionMatchesCurrentConfig = resolvedConfigKey === currentConfigKey;
|
|
186
|
+
const needsConsent = shouldShowConnectionReview(isDevPlayer, requiresConnectionConsent, resourceConsents.length > 0);
|
|
119
187
|
|
|
120
188
|
// Called by ConnectionSetupScreen once every missing connector is resolved.
|
|
121
|
-
const handleSetupComplete = useCallback(resolvedIds => {
|
|
189
|
+
const handleSetupComplete = useCallback(async resolvedIds => {
|
|
122
190
|
const refs = {};
|
|
123
|
-
for (const [
|
|
124
|
-
const
|
|
125
|
-
if (!
|
|
126
|
-
const
|
|
127
|
-
const
|
|
191
|
+
for (const [localRefId, connectionId] of Object.entries(resolvedIds)) {
|
|
192
|
+
const requirement = connectionRequirements.find(item => item.localRefId === localRefId);
|
|
193
|
+
if (!requirement) continue;
|
|
194
|
+
const apiId = requirement.apiId;
|
|
195
|
+
const dataSources = requirement.dataSources;
|
|
196
|
+
const runtimeUrl = dataSources.map(ds => connectionRefsRef.current[ds]?.runtimeUrl ?? (isDevPlayer ? schemaMap[ds]?.properties?.primaryRuntimeUrl : undefined)).find(Boolean) ?? '';
|
|
128
197
|
for (const ds of dataSources) {
|
|
129
198
|
refs[ds] = {
|
|
130
199
|
apiId,
|
|
@@ -138,7 +207,44 @@ export function PowerAppsHostProvider({
|
|
|
138
207
|
}
|
|
139
208
|
}
|
|
140
209
|
setExtraConnectionRefs(refs);
|
|
141
|
-
|
|
210
|
+
const mergedForConsent = {
|
|
211
|
+
...connectionRefsRef.current,
|
|
212
|
+
...refs
|
|
213
|
+
};
|
|
214
|
+
const usages = connectionRequirements.map(requirement => ({
|
|
215
|
+
localRefId: requirement.localRefId,
|
|
216
|
+
apiId: requirement.apiId,
|
|
217
|
+
connectionId: requirement.dataSources.map(dataSource => mergedForConsent[dataSource]?.connectionName).find(Boolean) ?? '',
|
|
218
|
+
dataSources: requirement.dataSources,
|
|
219
|
+
appActions: requirement.appActions ?? [],
|
|
220
|
+
apiScopes: requirement.apiScopes
|
|
221
|
+
}));
|
|
222
|
+
if (usages.some(usage => !usage.connectionId)) {
|
|
223
|
+
throw new Error('Not all required connections have been resolved.');
|
|
224
|
+
}
|
|
225
|
+
if (!isDevPlayer && resourceConsents.length > 0 && !onResourceConsent) {
|
|
226
|
+
throw new Error('Protected resource consent requires an onResourceConsent persistence callback.');
|
|
227
|
+
}
|
|
228
|
+
const shouldPersistUsages = shouldPersistConnectionUsages(isDevPlayer, appId);
|
|
229
|
+
if (shouldPersistUsages && appId && usages.length > 0) {
|
|
230
|
+
const token = await acquireToken([powerPlatformScope(region)]);
|
|
231
|
+
if (!token) throw new Error('Unable to acquire a token to register connection usage.');
|
|
232
|
+
await Promise.all(usages.map(usage => postConnectionUsage(appId, usage, environmentId, region, token)));
|
|
233
|
+
await reportScopeConsents(appId, usages, environmentId, region, token);
|
|
234
|
+
}
|
|
235
|
+
if (!isDevPlayer) {
|
|
236
|
+
await onConnectionConsent?.(usages);
|
|
237
|
+
await onResourceConsent?.(resourceConsents);
|
|
238
|
+
}
|
|
239
|
+
setConsentApproved(true);
|
|
240
|
+
}, [acquireToken, appId, environmentId, region, schemaMap, connectionRequirements, onConnectionConsent, onResourceConsent, resourceConsents, isDevPlayer]);
|
|
241
|
+
const consentDetails = useMemo(() => connectionRequirements.map(requirement => ({
|
|
242
|
+
localRefId: requirement.localRefId,
|
|
243
|
+
connectorId: requirement.connectorId,
|
|
244
|
+
displayName: requirement.displayName ?? requirement.connectorId.replace(/^shared_/, ''),
|
|
245
|
+
description: `Uses ${requirement.dataSources.join(', ')}`,
|
|
246
|
+
appActions: requirement.appActions ?? []
|
|
247
|
+
})), [connectionRequirements]);
|
|
142
248
|
|
|
143
249
|
// ---- Bridge wiring ----
|
|
144
250
|
|
|
@@ -150,7 +256,7 @@ export function PowerAppsHostProvider({
|
|
|
150
256
|
connectionRefsRef.current = connectionRefs;
|
|
151
257
|
const configuredBridgeInputsRef = useRef(undefined);
|
|
152
258
|
const [, setBridgeRegistrationVersion] = useState(0);
|
|
153
|
-
const hasResolvedConnections = isSignedIn && isReady && (missingConnectors.length === 0 ||
|
|
259
|
+
const hasResolvedConnections = isSignedIn && isReady && resolutionMatchesCurrentConfig && (missingConnectors.length === 0 || consentApproved) && (!needsConsent || consentApproved);
|
|
154
260
|
const isBridgeConfiguredFor = (connections, dataSources) => {
|
|
155
261
|
const currentInputs = configuredBridgeInputsRef.current;
|
|
156
262
|
return currentInputs?.connections === connections && currentInputs.dataSources === dataSources;
|
|
@@ -250,13 +356,34 @@ export function PowerAppsHostProvider({
|
|
|
250
356
|
}
|
|
251
357
|
}, [isSignedIn, isReady]);
|
|
252
358
|
useEffect(() => {
|
|
253
|
-
if (isSignedIn && isReady && missingConnectors.length > 0 && !setupDone) {
|
|
359
|
+
if (isSignedIn && isReady && (missingConnectors.length > 0 || needsConsent) && !setupDone) {
|
|
254
360
|
debugLog(`[PAHost] render: showing ConnectionSetupScreen for ${missingConnectors.length} missing connector(s):`, missingConnectors.join(', '));
|
|
255
361
|
}
|
|
256
|
-
}, [isSignedIn, isReady, missingConnectors, setupDone]);
|
|
362
|
+
}, [isSignedIn, isReady, missingConnectors, needsConsent, setupDone]);
|
|
257
363
|
|
|
258
364
|
// ---- Render ----
|
|
259
365
|
|
|
366
|
+
if (connectionError) {
|
|
367
|
+
const accessDenied = connectionErrorKind === 'app-access-denied';
|
|
368
|
+
return renderConnectionError(accessDenied ? 'You don’t have access' : 'Unable to load app', [connectionError], accessDenied);
|
|
369
|
+
}
|
|
370
|
+
if (configurationErrors.length > 0) {
|
|
371
|
+
return renderConnectionError('Connection configuration error', configurationErrors);
|
|
372
|
+
}
|
|
373
|
+
if (isSignedIn && !resolutionMatchesCurrentConfig) {
|
|
374
|
+
return /*#__PURE__*/_jsx(View, {
|
|
375
|
+
style: {
|
|
376
|
+
flex: 1,
|
|
377
|
+
alignItems: 'center',
|
|
378
|
+
justifyContent: 'center',
|
|
379
|
+
backgroundColor: '#F8F8F8'
|
|
380
|
+
},
|
|
381
|
+
children: /*#__PURE__*/_jsx(ActivityIndicator, {
|
|
382
|
+
size: "large"
|
|
383
|
+
})
|
|
384
|
+
});
|
|
385
|
+
}
|
|
386
|
+
|
|
260
387
|
// First mount: register the native bridge with static manifest refs before
|
|
261
388
|
// any route children can mount and call into @microsoft/power-apps.
|
|
262
389
|
if (!staticBridgeRegistered && !hasResolvedConnections) {
|
|
@@ -292,7 +419,7 @@ export function PowerAppsHostProvider({
|
|
|
292
419
|
// Wrapped in SafeAreaProvider so useSafeAreaInsets() works even when the
|
|
293
420
|
// consuming app hasn't mounted its own SafeAreaProvider yet. Nesting is
|
|
294
421
|
// safe — react-native-safe-area-context treats inner providers as no-ops.
|
|
295
|
-
if (isSignedIn && isReady && missingConnectors.length > 0 && !setupDone) {
|
|
422
|
+
if (isSignedIn && isReady && (missingConnectors.length > 0 || needsConsent) && !setupDone) {
|
|
296
423
|
return /*#__PURE__*/_jsx(SafeAreaProvider, {
|
|
297
424
|
children: /*#__PURE__*/_jsx(ConnectionSetupScreen, {
|
|
298
425
|
missingConnectors: missingEntries,
|
|
@@ -300,8 +427,13 @@ export function PowerAppsHostProvider({
|
|
|
300
427
|
environmentId: environmentId,
|
|
301
428
|
region: region,
|
|
302
429
|
acquireToken: acquireToken,
|
|
430
|
+
onSignOut: isSignedIn ? onSignOut : undefined,
|
|
303
431
|
isvClientId: isvClientId,
|
|
304
432
|
onAllConnected: handleSetupComplete,
|
|
433
|
+
consentDetails: consentDetails,
|
|
434
|
+
resourceConsents: resourceConsents,
|
|
435
|
+
onConsentDeclined: onConnectionConsentDeclined,
|
|
436
|
+
showReview: !isDevPlayer,
|
|
305
437
|
renderOAuthWebView: renderOAuthWebView
|
|
306
438
|
// No onSkip — connections are required.
|
|
307
439
|
})
|