@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.
Files changed (154) hide show
  1. package/README.md +21 -10
  2. package/compatibility/current.json +31 -4
  3. package/lib/NOTICE.txt +1325 -24
  4. package/lib/commonjs/auth/AuthContext.js +110 -33
  5. package/lib/commonjs/bridge/NativePowerAppsBridge.js +11 -18
  6. package/lib/commonjs/bridge/PowerPagesBridge.js +27 -8
  7. package/lib/commonjs/bridge/bridgeState.js +40 -0
  8. package/lib/commonjs/config/babelConfig.js +13 -0
  9. package/lib/commonjs/config/expoConfig.js +71 -0
  10. package/lib/commonjs/config/metroConfig.js +70 -0
  11. package/lib/commonjs/config/metroLogger.js +182 -0
  12. package/lib/commonjs/config/tamaguiConfig.js +98 -0
  13. package/lib/commonjs/connections/ConnectionSetupScreen.js +459 -80
  14. package/lib/commonjs/connections/PowerPlatformConnectionApi.js +946 -0
  15. package/lib/commonjs/connections/connectionMetadata.js +127 -0
  16. package/lib/commonjs/connections/connectionState.js +96 -0
  17. package/lib/commonjs/connections/disambiguate.js +19 -11
  18. package/lib/commonjs/connections/resolveConnections.js +348 -0
  19. package/lib/commonjs/connections/types.js +106 -0
  20. package/lib/commonjs/{platform → connections}/useConnectionRefs.js +130 -65
  21. package/lib/commonjs/connections/useConnectionSetup.js +111 -52
  22. package/lib/commonjs/controls/FilePicker.js +6 -0
  23. package/lib/commonjs/controls/ImagePicker.js +8 -0
  24. package/lib/commonjs/index.js +167 -4
  25. package/lib/commonjs/logger.js +9 -6
  26. package/lib/commonjs/offline/createOfflineHostActivationCallbacks.js +61 -0
  27. package/lib/commonjs/offline/useOfflineIntegrationWithStartupGate.js +48 -0
  28. package/lib/commonjs/offline/useOfflineIntegrationWithStartupGate.web.js +13 -0
  29. package/lib/commonjs/platform/DataverseDataSources.js +18 -0
  30. package/lib/commonjs/platform/DataverseGlobalDiscoveryApi.js +94 -0
  31. package/lib/commonjs/platform/GlobalDiscovery.js +51 -0
  32. package/lib/commonjs/platform/PowerPlatformEnvironmentApi.js +6 -106
  33. package/lib/commonjs/provider/PowerAppsHostProvider.js +157 -25
  34. package/lib/commonjs/provider/PowerAppsProvider.js +178 -134
  35. package/lib/commonjs/provider/SessionDetailsGesture.js +208 -0
  36. package/lib/commonjs/telemetry/OfflineSdkTelemetry.js +95 -0
  37. package/lib/commonjs/telemetry/OfflineSdkTelemetry.web.js +9 -0
  38. package/lib/commonjs/telemetry/index.js +22 -3
  39. package/lib/module/auth/AuthContext.js +110 -33
  40. package/lib/module/bridge/NativePowerAppsBridge.js +12 -19
  41. package/lib/module/bridge/PowerPagesBridge.js +27 -8
  42. package/lib/module/bridge/bridgeState.js +34 -0
  43. package/lib/module/config/babelConfig.js +9 -0
  44. package/lib/module/config/expoConfig.js +67 -0
  45. package/lib/module/config/metroConfig.js +65 -0
  46. package/lib/module/config/metroLogger.js +175 -0
  47. package/lib/module/config/tamaguiConfig.js +93 -0
  48. package/lib/module/connections/ConnectionSetupScreen.js +460 -81
  49. package/lib/module/connections/PowerPlatformConnectionApi.js +932 -0
  50. package/lib/module/connections/connectionMetadata.js +112 -0
  51. package/lib/module/connections/connectionState.js +82 -0
  52. package/lib/module/connections/disambiguate.js +19 -12
  53. package/lib/module/connections/resolveConnections.js +343 -0
  54. package/lib/module/connections/types.js +91 -0
  55. package/lib/module/{platform → connections}/useConnectionRefs.js +131 -66
  56. package/lib/module/connections/useConnectionSetup.js +113 -54
  57. package/lib/module/controls/FilePicker.js +6 -0
  58. package/lib/module/controls/ImagePicker.js +8 -0
  59. package/lib/module/index.js +14 -4
  60. package/lib/module/logger.js +8 -6
  61. package/lib/module/offline/createOfflineHostActivationCallbacks.js +55 -0
  62. package/lib/module/offline/useOfflineIntegrationWithStartupGate.js +44 -0
  63. package/lib/module/offline/useOfflineIntegrationWithStartupGate.web.js +9 -0
  64. package/lib/module/platform/DataverseDataSources.js +13 -0
  65. package/lib/module/platform/DataverseGlobalDiscoveryApi.js +89 -0
  66. package/lib/module/platform/GlobalDiscovery.js +45 -0
  67. package/lib/module/platform/PowerPlatformEnvironmentApi.js +5 -104
  68. package/lib/module/provider/PowerAppsHostProvider.js +160 -28
  69. package/lib/module/provider/PowerAppsProvider.js +182 -138
  70. package/lib/module/provider/SessionDetailsGesture.js +203 -0
  71. package/lib/module/telemetry/OfflineSdkTelemetry.js +91 -0
  72. package/lib/module/telemetry/OfflineSdkTelemetry.web.js +5 -0
  73. package/lib/module/telemetry/index.js +15 -4
  74. package/lib/typescript/commonjs/auth/AuthContext.d.ts +3 -1
  75. package/lib/typescript/commonjs/auth/AuthContext.web.d.ts +1 -0
  76. package/lib/typescript/commonjs/bridge/bridgeState.d.ts +5 -0
  77. package/lib/typescript/commonjs/config/babelConfig.d.ts +12 -0
  78. package/lib/typescript/commonjs/config/expoConfig.d.ts +21 -0
  79. package/lib/typescript/commonjs/config/metroConfig.d.ts +6 -0
  80. package/lib/typescript/commonjs/config/metroLogger.d.ts +21 -0
  81. package/lib/typescript/commonjs/config/tamaguiConfig.d.ts +1792 -0
  82. package/lib/typescript/commonjs/connections/ConnectionSetupScreen.d.ts +28 -7
  83. package/lib/typescript/commonjs/{platform → connections}/PowerPlatformConnectionApi.d.ts +52 -43
  84. package/lib/typescript/commonjs/connections/connectionMetadata.d.ts +66 -0
  85. package/lib/typescript/commonjs/connections/connectionState.d.ts +16 -0
  86. package/lib/typescript/commonjs/connections/disambiguate.d.ts +4 -2
  87. package/lib/typescript/commonjs/{platform → connections}/resolveConnections.d.ts +22 -20
  88. package/lib/typescript/commonjs/connections/types.d.ts +129 -1
  89. package/lib/typescript/commonjs/{platform → connections}/useConnectionRefs.d.ts +36 -25
  90. package/lib/typescript/commonjs/connections/useConnectionSetup.d.ts +12 -8
  91. package/lib/typescript/commonjs/controls/FilePicker.d.ts +7 -1
  92. package/lib/typescript/commonjs/controls/ImagePicker.d.ts +9 -1
  93. package/lib/typescript/commonjs/index.d.ts +18 -10
  94. package/lib/typescript/commonjs/logger.d.ts +4 -4
  95. package/lib/typescript/commonjs/offline/createOfflineHostActivationCallbacks.d.ts +12 -0
  96. package/lib/typescript/commonjs/offline/useOfflineIntegrationWithStartupGate.d.ts +15 -0
  97. package/lib/typescript/commonjs/offline/useOfflineIntegrationWithStartupGate.web.d.ts +6 -0
  98. package/lib/typescript/commonjs/platform/DataverseDataSources.d.ts +28 -0
  99. package/lib/typescript/commonjs/platform/DataverseGlobalDiscoveryApi.d.ts +3 -0
  100. package/lib/typescript/commonjs/platform/GlobalDiscovery.d.ts +4 -0
  101. package/lib/typescript/commonjs/platform/PowerPlatformEnvironmentApi.d.ts +3 -74
  102. package/lib/typescript/commonjs/provider/PowerAppsHostProvider.d.ts +29 -18
  103. package/lib/typescript/commonjs/provider/PowerAppsProvider.d.ts +54 -9
  104. package/lib/typescript/commonjs/provider/SessionDetailsGesture.d.ts +13 -0
  105. package/lib/typescript/commonjs/telemetry/OfflineSdkTelemetry.d.ts +15 -0
  106. package/lib/typescript/commonjs/telemetry/OfflineSdkTelemetry.web.d.ts +2 -0
  107. package/lib/typescript/commonjs/telemetry/index.d.ts +13 -1
  108. package/lib/typescript/module/auth/AuthContext.d.ts +3 -1
  109. package/lib/typescript/module/auth/AuthContext.web.d.ts +1 -0
  110. package/lib/typescript/module/bridge/bridgeState.d.ts +5 -0
  111. package/lib/typescript/module/config/babelConfig.d.ts +12 -0
  112. package/lib/typescript/module/config/expoConfig.d.ts +21 -0
  113. package/lib/typescript/module/config/metroConfig.d.ts +6 -0
  114. package/lib/typescript/module/config/metroLogger.d.ts +21 -0
  115. package/lib/typescript/module/config/tamaguiConfig.d.ts +1792 -0
  116. package/lib/typescript/module/connections/ConnectionSetupScreen.d.ts +28 -7
  117. package/lib/typescript/module/{platform → connections}/PowerPlatformConnectionApi.d.ts +52 -43
  118. package/lib/typescript/module/connections/connectionMetadata.d.ts +66 -0
  119. package/lib/typescript/module/connections/connectionState.d.ts +16 -0
  120. package/lib/typescript/module/connections/disambiguate.d.ts +4 -2
  121. package/lib/typescript/module/{platform → connections}/resolveConnections.d.ts +22 -20
  122. package/lib/typescript/module/connections/types.d.ts +129 -1
  123. package/lib/typescript/module/{platform → connections}/useConnectionRefs.d.ts +36 -25
  124. package/lib/typescript/module/connections/useConnectionSetup.d.ts +12 -8
  125. package/lib/typescript/module/controls/FilePicker.d.ts +7 -1
  126. package/lib/typescript/module/controls/ImagePicker.d.ts +9 -1
  127. package/lib/typescript/module/index.d.ts +18 -10
  128. package/lib/typescript/module/logger.d.ts +4 -4
  129. package/lib/typescript/module/offline/createOfflineHostActivationCallbacks.d.ts +12 -0
  130. package/lib/typescript/module/offline/useOfflineIntegrationWithStartupGate.d.ts +15 -0
  131. package/lib/typescript/module/offline/useOfflineIntegrationWithStartupGate.web.d.ts +6 -0
  132. package/lib/typescript/module/platform/DataverseDataSources.d.ts +28 -0
  133. package/lib/typescript/module/platform/DataverseGlobalDiscoveryApi.d.ts +3 -0
  134. package/lib/typescript/module/platform/GlobalDiscovery.d.ts +4 -0
  135. package/lib/typescript/module/platform/PowerPlatformEnvironmentApi.d.ts +3 -74
  136. package/lib/typescript/module/provider/PowerAppsHostProvider.d.ts +29 -18
  137. package/lib/typescript/module/provider/PowerAppsProvider.d.ts +54 -9
  138. package/lib/typescript/module/provider/SessionDetailsGesture.d.ts +13 -0
  139. package/lib/typescript/module/telemetry/OfflineSdkTelemetry.d.ts +15 -0
  140. package/lib/typescript/module/telemetry/OfflineSdkTelemetry.web.d.ts +2 -0
  141. package/lib/typescript/module/telemetry/index.d.ts +13 -1
  142. package/package.json +52 -13
  143. package/scripts/build-codegen-package.js +283 -0
  144. package/scripts/start-web.mjs +9 -5
  145. package/scripts/upgrade/runtime.js +139 -24
  146. package/scripts/upgrade-authoring.js +274 -180
  147. package/scripts/upgrade.js +10 -16
  148. package/src/config/generated-config.d.ts +4 -0
  149. package/src/config/index.d.ts +3 -0
  150. package/src/config/tsconfig.json +26 -0
  151. package/lib/commonjs/platform/PowerPlatformConnectionApi.js +0 -606
  152. package/lib/commonjs/platform/resolveConnections.js +0 -264
  153. package/lib/module/platform/PowerPlatformConnectionApi.js +0 -595
  154. 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
- * Mirrors the Host SDK's DefaultAppMetadataProvider.fetchAppMetadataFromPowerConfig
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
- * Fetch the Dataverse instanceUrl for a Power Platform environment by calling
100
- * the environment-scoped Power Platform management API — the same call that
101
- * the Host SDK's DefaultAppMetadataProvider makes to populate
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 "../platform/useConnectionRefs.js";
38
- import { buildCdsDataSources } from "../platform/PowerPlatformEnvironmentApi.js";
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
- // setupDone is derived — true as soon as extraConnectionRefs is populated.
72
- const setupDone = Object.keys(extraConnectionRefs).length > 0;
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
- dataverseOrgUrl
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(() => missingConnectors.map(apiId => {
113
- const localRefId = Object.keys(connectionReferences).find(k => connectionReferences[k].id === apiId) ?? apiId;
114
- return {
115
- apiId,
116
- localRefId
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 [apiId, connectionId] of Object.entries(resolvedIds)) {
124
- const localRefId = Object.keys(connectionReferences).find(k => connectionReferences[k].id === apiId);
125
- if (!localRefId) continue;
126
- const dataSources = connectionReferences[localRefId].dataSources;
127
- const runtimeUrl = dataSources.map(ds => schemaMap[ds]?.properties?.primaryRuntimeUrl).find(Boolean) ?? '';
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
- }, [connectionReferences, schemaMap]);
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 || setupDone);
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
  })