@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.
Files changed (129) hide show
  1. package/README.md +21 -2
  2. package/compatibility/current.json +0 -1
  3. package/lib/NOTICE.txt +6 -12
  4. package/lib/commonjs/auth/AuthContext.js +74 -18
  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/connections/ConnectionSetupScreen.js +459 -80
  9. package/lib/commonjs/connections/PowerPlatformConnectionApi.js +946 -0
  10. package/lib/commonjs/connections/connectionMetadata.js +127 -0
  11. package/lib/commonjs/connections/connectionState.js +96 -0
  12. package/lib/commonjs/connections/disambiguate.js +19 -11
  13. package/lib/commonjs/connections/resolveConnections.js +347 -0
  14. package/lib/commonjs/connections/types.js +106 -0
  15. package/lib/commonjs/{platform → connections}/useConnectionRefs.js +128 -65
  16. package/lib/commonjs/connections/useConnectionSetup.js +111 -52
  17. package/lib/commonjs/controls/FilePicker.js +6 -0
  18. package/lib/commonjs/controls/ImagePicker.js +8 -0
  19. package/lib/commonjs/index.js +166 -4
  20. package/lib/commonjs/logger.js +9 -6
  21. package/lib/commonjs/offline/createOfflineHostActivationCallbacks.js +61 -0
  22. package/lib/commonjs/offline/useOfflineIntegrationWithStartupGate.js +46 -0
  23. package/lib/commonjs/offline/useOfflineIntegrationWithStartupGate.web.js +13 -0
  24. package/lib/commonjs/platform/DataverseDataSources.js +14 -0
  25. package/lib/commonjs/platform/DataverseGlobalDiscoveryApi.js +49 -0
  26. package/lib/commonjs/platform/GlobalDiscovery.js +51 -0
  27. package/lib/commonjs/platform/PowerPlatformEnvironmentApi.js +6 -106
  28. package/lib/commonjs/provider/PowerAppsHostProvider.js +156 -25
  29. package/lib/commonjs/provider/PowerAppsProvider.js +173 -134
  30. package/lib/commonjs/provider/SessionDetailsGesture.js +216 -0
  31. package/lib/commonjs/telemetry/OfflineSdkTelemetry.js +95 -0
  32. package/lib/commonjs/telemetry/OfflineSdkTelemetry.web.js +9 -0
  33. package/lib/commonjs/telemetry/index.js +22 -3
  34. package/lib/module/auth/AuthContext.js +74 -18
  35. package/lib/module/bridge/NativePowerAppsBridge.js +12 -19
  36. package/lib/module/bridge/PowerPagesBridge.js +27 -8
  37. package/lib/module/bridge/bridgeState.js +34 -0
  38. package/lib/module/connections/ConnectionSetupScreen.js +460 -81
  39. package/lib/module/connections/PowerPlatformConnectionApi.js +932 -0
  40. package/lib/module/connections/connectionMetadata.js +112 -0
  41. package/lib/module/connections/connectionState.js +82 -0
  42. package/lib/module/connections/disambiguate.js +19 -12
  43. package/lib/module/connections/resolveConnections.js +342 -0
  44. package/lib/module/connections/types.js +91 -0
  45. package/lib/module/{platform → connections}/useConnectionRefs.js +129 -66
  46. package/lib/module/connections/useConnectionSetup.js +113 -54
  47. package/lib/module/controls/FilePicker.js +6 -0
  48. package/lib/module/controls/ImagePicker.js +8 -0
  49. package/lib/module/index.js +10 -4
  50. package/lib/module/logger.js +8 -6
  51. package/lib/module/offline/createOfflineHostActivationCallbacks.js +55 -0
  52. package/lib/module/offline/useOfflineIntegrationWithStartupGate.js +42 -0
  53. package/lib/module/offline/useOfflineIntegrationWithStartupGate.web.js +9 -0
  54. package/lib/module/platform/DataverseDataSources.js +10 -0
  55. package/lib/module/platform/DataverseGlobalDiscoveryApi.js +45 -0
  56. package/lib/module/platform/GlobalDiscovery.js +45 -0
  57. package/lib/module/platform/PowerPlatformEnvironmentApi.js +5 -104
  58. package/lib/module/provider/PowerAppsHostProvider.js +159 -28
  59. package/lib/module/provider/PowerAppsProvider.js +177 -138
  60. package/lib/module/provider/SessionDetailsGesture.js +211 -0
  61. package/lib/module/telemetry/OfflineSdkTelemetry.js +91 -0
  62. package/lib/module/telemetry/OfflineSdkTelemetry.web.js +5 -0
  63. package/lib/module/telemetry/index.js +15 -4
  64. package/lib/typescript/commonjs/auth/AuthContext.d.ts +3 -1
  65. package/lib/typescript/commonjs/auth/AuthContext.web.d.ts +1 -0
  66. package/lib/typescript/commonjs/bridge/bridgeState.d.ts +5 -0
  67. package/lib/typescript/commonjs/connections/ConnectionSetupScreen.d.ts +28 -7
  68. package/lib/typescript/commonjs/{platform → connections}/PowerPlatformConnectionApi.d.ts +52 -43
  69. package/lib/typescript/commonjs/connections/connectionMetadata.d.ts +66 -0
  70. package/lib/typescript/commonjs/connections/connectionState.d.ts +16 -0
  71. package/lib/typescript/commonjs/connections/disambiguate.d.ts +4 -2
  72. package/lib/typescript/commonjs/{platform → connections}/resolveConnections.d.ts +19 -19
  73. package/lib/typescript/commonjs/connections/types.d.ts +129 -1
  74. package/lib/typescript/commonjs/{platform → connections}/useConnectionRefs.d.ts +33 -24
  75. package/lib/typescript/commonjs/connections/useConnectionSetup.d.ts +12 -8
  76. package/lib/typescript/commonjs/controls/FilePicker.d.ts +7 -1
  77. package/lib/typescript/commonjs/controls/ImagePicker.d.ts +9 -1
  78. package/lib/typescript/commonjs/index.d.ts +17 -10
  79. package/lib/typescript/commonjs/logger.d.ts +4 -4
  80. package/lib/typescript/commonjs/offline/createOfflineHostActivationCallbacks.d.ts +12 -0
  81. package/lib/typescript/commonjs/offline/useOfflineIntegrationWithStartupGate.d.ts +15 -0
  82. package/lib/typescript/commonjs/offline/useOfflineIntegrationWithStartupGate.web.d.ts +6 -0
  83. package/lib/typescript/commonjs/platform/DataverseDataSources.d.ts +27 -0
  84. package/lib/typescript/commonjs/platform/DataverseGlobalDiscoveryApi.d.ts +3 -0
  85. package/lib/typescript/commonjs/platform/GlobalDiscovery.d.ts +4 -0
  86. package/lib/typescript/commonjs/platform/PowerPlatformEnvironmentApi.d.ts +3 -74
  87. package/lib/typescript/commonjs/provider/PowerAppsHostProvider.d.ts +29 -18
  88. package/lib/typescript/commonjs/provider/PowerAppsProvider.d.ts +30 -8
  89. package/lib/typescript/commonjs/provider/SessionDetailsGesture.d.ts +13 -0
  90. package/lib/typescript/commonjs/telemetry/OfflineSdkTelemetry.d.ts +15 -0
  91. package/lib/typescript/commonjs/telemetry/OfflineSdkTelemetry.web.d.ts +2 -0
  92. package/lib/typescript/commonjs/telemetry/index.d.ts +13 -1
  93. package/lib/typescript/module/auth/AuthContext.d.ts +3 -1
  94. package/lib/typescript/module/auth/AuthContext.web.d.ts +1 -0
  95. package/lib/typescript/module/bridge/bridgeState.d.ts +5 -0
  96. package/lib/typescript/module/connections/ConnectionSetupScreen.d.ts +28 -7
  97. package/lib/typescript/module/{platform → connections}/PowerPlatformConnectionApi.d.ts +52 -43
  98. package/lib/typescript/module/connections/connectionMetadata.d.ts +66 -0
  99. package/lib/typescript/module/connections/connectionState.d.ts +16 -0
  100. package/lib/typescript/module/connections/disambiguate.d.ts +4 -2
  101. package/lib/typescript/module/{platform → connections}/resolveConnections.d.ts +19 -19
  102. package/lib/typescript/module/connections/types.d.ts +129 -1
  103. package/lib/typescript/module/{platform → connections}/useConnectionRefs.d.ts +33 -24
  104. package/lib/typescript/module/connections/useConnectionSetup.d.ts +12 -8
  105. package/lib/typescript/module/controls/FilePicker.d.ts +7 -1
  106. package/lib/typescript/module/controls/ImagePicker.d.ts +9 -1
  107. package/lib/typescript/module/index.d.ts +17 -10
  108. package/lib/typescript/module/logger.d.ts +4 -4
  109. package/lib/typescript/module/offline/createOfflineHostActivationCallbacks.d.ts +12 -0
  110. package/lib/typescript/module/offline/useOfflineIntegrationWithStartupGate.d.ts +15 -0
  111. package/lib/typescript/module/offline/useOfflineIntegrationWithStartupGate.web.d.ts +6 -0
  112. package/lib/typescript/module/platform/DataverseDataSources.d.ts +27 -0
  113. package/lib/typescript/module/platform/DataverseGlobalDiscoveryApi.d.ts +3 -0
  114. package/lib/typescript/module/platform/GlobalDiscovery.d.ts +4 -0
  115. package/lib/typescript/module/platform/PowerPlatformEnvironmentApi.d.ts +3 -74
  116. package/lib/typescript/module/provider/PowerAppsHostProvider.d.ts +29 -18
  117. package/lib/typescript/module/provider/PowerAppsProvider.d.ts +30 -8
  118. package/lib/typescript/module/provider/SessionDetailsGesture.d.ts +13 -0
  119. package/lib/typescript/module/telemetry/OfflineSdkTelemetry.d.ts +15 -0
  120. package/lib/typescript/module/telemetry/OfflineSdkTelemetry.web.d.ts +2 -0
  121. package/lib/typescript/module/telemetry/index.d.ts +13 -1
  122. package/package.json +11 -6
  123. package/scripts/metro-logger.js +213 -0
  124. package/scripts/upgrade/runtime.js +13 -7
  125. package/scripts/upgrade-authoring.js +5 -6
  126. package/lib/commonjs/platform/PowerPlatformConnectionApi.js +0 -606
  127. package/lib/commonjs/platform/resolveConnections.js +0 -264
  128. package/lib/module/platform/PowerPlatformConnectionApi.js +0 -595
  129. package/lib/module/platform/resolveConnections.js +0 -259
@@ -0,0 +1,61 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.createHostQueryClient = createHostQueryClient;
7
+ exports.createOfflineHostActivationCallbacks = createOfflineHostActivationCallbacks;
8
+ exports.resolveSdkMountDecision = resolveSdkMountDecision;
9
+ var _reactQuery = require("@tanstack/react-query");
10
+ function setNetworkMode(queryClient, networkMode) {
11
+ const current = queryClient.getDefaultOptions();
12
+ queryClient.setDefaultOptions({
13
+ ...current,
14
+ queries: {
15
+ ...current.queries,
16
+ staleTime: 60_000,
17
+ retry: 2,
18
+ networkMode
19
+ },
20
+ mutations: {
21
+ ...current.mutations,
22
+ retry: 0,
23
+ networkMode
24
+ }
25
+ });
26
+ }
27
+ function createHostQueryClient() {
28
+ return new _reactQuery.QueryClient({
29
+ defaultOptions: {
30
+ queries: {
31
+ staleTime: 60_000,
32
+ retry: 2
33
+ },
34
+ mutations: {
35
+ retry: 0
36
+ }
37
+ }
38
+ });
39
+ }
40
+ function createOfflineHostActivationCallbacks(queryClient) {
41
+ return {
42
+ prepareForOfflineActivation: () => queryClient.cancelQueries(),
43
+ completeOfflineActivation: async () => {
44
+ setNetworkMode(queryClient, 'always');
45
+ await queryClient.invalidateQueries();
46
+ },
47
+ rollbackOfflineActivation: async () => {
48
+ setNetworkMode(queryClient, undefined);
49
+ await queryClient.invalidateQueries();
50
+ }
51
+ };
52
+ }
53
+ function resolveSdkMountDecision(value) {
54
+ return typeof value === 'boolean' ? {
55
+ shouldMountChildren: value,
56
+ contractSupported: true
57
+ } : {
58
+ shouldMountChildren: true,
59
+ contractSupported: false
60
+ };
61
+ }
@@ -0,0 +1,46 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.useOfflineIntegrationWithStartupGate = useOfflineIntegrationWithStartupGate;
7
+ var _react = require("react");
8
+ var _reactQuery = require("@tanstack/react-query");
9
+ var _powerAppsNativeCommon = require("@microsoft/power-apps-native-common");
10
+ var _DataverseGlobalDiscoveryApi = require("../platform/DataverseGlobalDiscoveryApi.js");
11
+ var _index = require("../telemetry/index.js");
12
+ var _createOfflineHostActivationCallbacks = require("./createOfflineHostActivationCallbacks.js");
13
+ var _useOfflineIntegration = require("./useOfflineIntegration");
14
+ function useOfflineIntegrationWithStartupGate({
15
+ powerConfig,
16
+ offlineProfile,
17
+ acquireToken,
18
+ hasRealAccount,
19
+ isAuthReady
20
+ }) {
21
+ const queryClient = (0, _reactQuery.useQueryClient)();
22
+ const resolveInstanceUrl = (0, _react.useCallback)(async (environmentId, region, acqToken) => (await (0, _DataverseGlobalDiscoveryApi.fetchInstanceUrl)(environmentId, region, acqToken)) ?? null, []);
23
+ const hostActivationCallbacks = (0, _react.useMemo)(() => (0, _createOfflineHostActivationCallbacks.createOfflineHostActivationCallbacks)(queryClient), [queryClient]);
24
+ const offline = (0, _useOfflineIntegration.useOfflineIntegration)({
25
+ powerConfig,
26
+ offlineProfile,
27
+ acquireToken,
28
+ isSignedIn: hasRealAccount,
29
+ authState: isAuthReady ? hasRealAccount ? 'authenticated' : 'signed-out' : 'hydrating',
30
+ fetchInstanceUrl: resolveInstanceUrl,
31
+ hostActivationCallbacks
32
+ });
33
+ const mountDecision = (0, _createOfflineHostActivationCallbacks.resolveSdkMountDecision)(offline.shouldMountChildren);
34
+ (0, _react.useEffect)(() => {
35
+ if (!mountDecision.contractSupported) {
36
+ (0, _powerAppsNativeCommon.getAppLogger)().trackErrorEvent(_index.HostOperationNames.OfflineRoutingContractUnsupported, {
37
+ fallbackRoute: 'online'
38
+ });
39
+ }
40
+ }, [mountDecision.contractSupported]);
41
+ return {
42
+ isEnabled: offline.isEnabled,
43
+ StatusOverlay: offline.StatusOverlay,
44
+ shouldMountChildren: mountDecision.shouldMountChildren
45
+ };
46
+ }
@@ -0,0 +1,13 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.useOfflineIntegrationWithStartupGate = useOfflineIntegrationWithStartupGate;
7
+ function useOfflineIntegrationWithStartupGate(_options) {
8
+ return {
9
+ isEnabled: false,
10
+ StatusOverlay: null,
11
+ shouldMountChildren: true
12
+ };
13
+ }
@@ -0,0 +1,14 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.buildCdsDataSources = buildCdsDataSources;
7
+ function buildCdsDataSources(databaseReferences, dataverseOrgUrl) {
8
+ return Object.values(databaseReferences).flatMap(databaseReference => Object.values(databaseReference.dataSources ?? {}).map(dataSource => ({
9
+ runtimeUrl: dataverseOrgUrl ? `${dataverseOrgUrl}/api/data/v9.0/` : '',
10
+ entitySetName: dataSource.entitySetName ?? '',
11
+ logicalName: dataSource.logicalName ?? '',
12
+ version: '9.2'
13
+ })));
14
+ }
@@ -0,0 +1,49 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.fetchInstanceUrl = fetchInstanceUrl;
7
+ var _http = require("@microsoft/power-apps-native-common/http");
8
+ var _index = require("../telemetry/index.js");
9
+ var _GlobalDiscovery = require("./GlobalDiscovery.js");
10
+ function getSafeErrorName(error) {
11
+ return error instanceof Error ? error.constructor.name : typeof error;
12
+ }
13
+
14
+ /** Fetch the Dataverse instance URL from the Dataverse Global Discovery Service. */
15
+ async function fetchInstanceUrl(environmentId, region, acquireToken, _bearerToken) {
16
+ const scenario = (0, _index.getAppLogger)().trackScenario(_index.HostOperationNames.PlatformFetchInstanceUrl, {
17
+ environmentId,
18
+ region
19
+ });
20
+ try {
21
+ const url = (0, _GlobalDiscovery.buildGlobalDiscoveryUrl)(environmentId, region);
22
+ const token = await acquireToken([(0, _GlobalDiscovery.globalDiscoveryScope)(region)]);
23
+ if (!token) {
24
+ throw new Error('fetchInstanceUrl: failed to acquire Dataverse Global Discovery token');
25
+ }
26
+ const response = await _index.platformHttpClient.request(url, {
27
+ method: 'GET',
28
+ headers: {
29
+ Authorization: `Bearer ${token}`,
30
+ 'Content-Type': 'application/json'
31
+ }
32
+ });
33
+ if (!response.ok) {
34
+ throw new Error(`fetchInstanceUrl: Global Discovery returned ${response.status} ${response.statusText}`);
35
+ }
36
+ const result = await response.json();
37
+ const instanceUrl = result.value?.find(instance => instance.EnvironmentId?.toLowerCase() === environmentId.toLowerCase())?.ApiUrl;
38
+ scenario.complete({
39
+ hasInstanceUrl: instanceUrl !== undefined,
40
+ ...(0, _http.getHttpCorrelationProperties)(response.headers)
41
+ });
42
+ return instanceUrl;
43
+ } catch (error) {
44
+ scenario.fail(undefined, {
45
+ errorName: getSafeErrorName(error)
46
+ });
47
+ throw error;
48
+ }
49
+ }
@@ -0,0 +1,51 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.buildGlobalDiscoveryUrl = buildGlobalDiscoveryUrl;
7
+ exports.globalDiscoveryEndpoint = globalDiscoveryEndpoint;
8
+ exports.globalDiscoveryScope = globalDiscoveryScope;
9
+ const GLOBAL_DISCOVERY_HOST = {
10
+ prod: 'globaldisco.crm.dynamics.com',
11
+ gcc: 'globaldisco.crm9.dynamics.com',
12
+ usg: 'globaldisco.crm.microsoftdynamics.us',
13
+ dod: 'globaldisco.crm.appsplatform.us',
14
+ china: 'globaldisco.crm.dynamics.cn'
15
+ };
16
+ const REGION_DISCOVERY_CLOUD = {
17
+ dev: 'prod',
18
+ test: 'prod',
19
+ preprod: 'prod',
20
+ preview: 'prod',
21
+ firstrelease: 'prod',
22
+ prod: 'prod',
23
+ gccmoderate: 'gcc',
24
+ gov: 'gcc',
25
+ gcchigh: 'usg',
26
+ high: 'usg',
27
+ dod: 'dod',
28
+ mooncake: 'china'
29
+ };
30
+ 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;
31
+ function globalDiscoveryEndpoint(region) {
32
+ const cloud = REGION_DISCOVERY_CLOUD[region];
33
+ if (!cloud) {
34
+ throw new Error(`Dataverse Global Discovery is not available for Power Platform region "${region}"`);
35
+ }
36
+ return `https://${GLOBAL_DISCOVERY_HOST[cloud]}`;
37
+ }
38
+ function globalDiscoveryScope(region) {
39
+ return `${globalDiscoveryEndpoint(region)}/user_impersonation`;
40
+ }
41
+ function buildGlobalDiscoveryUrl(environmentId, region) {
42
+ if (!ENVIRONMENT_ID_PATTERN.test(environmentId)) {
43
+ throw new Error(`Invalid Power Platform environment ID: "${environmentId}"`);
44
+ }
45
+ const normalizedEnvironmentId = environmentId.toLowerCase();
46
+ const query = new URLSearchParams({
47
+ '$select': 'ApiUrl,EnvironmentId',
48
+ '$filter': `EnvironmentId eq '${normalizedEnvironmentId}'`
49
+ });
50
+ return `${globalDiscoveryEndpoint(region)}/api/discovery/v2.0/Instances?${query.toString()}`;
51
+ }
@@ -4,22 +4,13 @@ Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
6
  exports.POWER_PLATFORM_API_SCOPE = void 0;
7
- exports.buildCdsDataSources = buildCdsDataSources;
8
7
  exports.buildEnvironmentEndpoint = buildEnvironmentEndpoint;
9
- exports.fetchInstanceUrl = fetchInstanceUrl;
8
+ exports.connectionUserConsentScope = connectionUserConsentScope;
10
9
  exports.powerPlatformScope = powerPlatformScope;
11
- var _index = require("../telemetry/index.js");
12
10
  /*!
13
11
  * power-apps-native-host — PowerPlatformEnvironmentApi
14
12
  *
15
- * Mirrors the Host SDK's DefaultAppMetadataProvider.fetchAppMetadataFromPowerConfig
16
- * pattern: resolves the Power Platform environment-scoped API endpoint from the
17
- * environment GUID and region, then fetches linkedEnvironmentMetadata to obtain
18
- * the Dataverse instanceUrl.
19
- *
20
- * Reference (Host SDK):
21
- * packages/power-apps-host/src/providers/defaults/DefaultAppMetadataProvider.ts
22
- * packages/power-apps-common/src/services/PowerPlatformApiDiscovery.ts
13
+ * Resolves Power Platform RP endpoints and scopes.
23
14
  */
24
15
 
25
16
  // ---------------------------------------------------------------------------
@@ -78,10 +69,6 @@ function buildEnvironmentEndpoint(environmentId, region) {
78
69
  return `https://${hexPrefix}.${hexSuffix}.environment.${hostNameSuffix}`;
79
70
  }
80
71
 
81
- // ---------------------------------------------------------------------------
82
- // Public API
83
- // ---------------------------------------------------------------------------
84
-
85
72
  /**
86
73
  * Token scope for the Power Platform management API (prod cluster).
87
74
  * Non-prod clusters (test, gov, high, dev, mooncake, etc.) host the management
@@ -99,96 +86,9 @@ function powerPlatformScope(region) {
99
86
  const host = HOST_NAME_SUFFIX[region] ?? HOST_NAME_SUFFIX['prod'];
100
87
  return `https://${host}/.default`;
101
88
  }
102
- function getSafeErrorName(error) {
103
- return error instanceof Error ? error.constructor.name : typeof error;
104
- }
105
89
 
106
- /**
107
- * Fetch the Dataverse instanceUrl for a Power Platform environment by calling
108
- * the environment-scoped Power Platform management API — the same call that
109
- * the Host SDK's DefaultAppMetadataProvider makes to populate
110
- * `databaseReferences[*].databaseDetails.linkedEnvironmentMetadata`.
111
- *
112
- * @param environmentId - Environment GUID (from power.config.json `environmentId`).
113
- * @param region - Cluster region string (from power.config.json `region`, default "prod").
114
- * @param acquireToken - Callback to obtain a bearer token for the given scopes.
115
- * Scope will be {@link powerPlatformScope}`(region)`.
116
- * @param bearerToken - Optional pre-fetched bearer token for {@link powerPlatformScope}`(region)`.
117
- * Pass this to avoid a redundant `acquireToken` round-trip when the
118
- * caller has already obtained a token for the same audience.
119
- * @returns The Dataverse org URL (e.g. "https://org12345.crm.dynamics.com"), or
120
- * `undefined` if the environment has no linked CDS database.
121
- */
122
- async function fetchInstanceUrl(environmentId, region, acquireToken, bearerToken) {
123
- const scenario = (0, _index.getAppLogger)().trackScenario(_index.HostOperationNames.PlatformFetchInstanceUrl, {
124
- environmentId,
125
- region
126
- });
127
- try {
128
- const baseUrl = buildEnvironmentEndpoint(environmentId, region);
129
- const url = `${baseUrl}/powerapps/environment?api-version=1&%24filter=name+eq+%27${encodeURIComponent(environmentId)}%27`;
130
- const token = bearerToken ?? (await acquireToken([powerPlatformScope(region)]));
131
- if (!token) {
132
- throw new Error('fetchInstanceUrl: failed to acquire Power Platform API token');
133
- }
134
- const response = await fetch(url, {
135
- method: 'GET',
136
- headers: {
137
- Authorization: `Bearer ${token}`,
138
- 'Content-Type': 'application/json'
139
- }
140
- });
141
- if (!response.ok) {
142
- throw new Error(`fetchInstanceUrl: environment API returned ${response.status} ${response.statusText}`);
143
- }
144
- const env = await response.json();
145
- const instanceUrl = env.properties?.linkedEnvironmentMetadata?.instanceUrl;
146
- scenario.complete({
147
- hasInstanceUrl: instanceUrl !== undefined
148
- });
149
- return instanceUrl;
150
- } catch (e) {
151
- scenario.fail(undefined, {
152
- errorName: getSafeErrorName(e)
153
- });
154
- throw e;
155
- }
156
- }
157
-
158
- // ---------------------------------------------------------------------------
159
- // buildCdsDataSources
160
- // ---------------------------------------------------------------------------
161
-
162
- /**
163
- * Shape of a single Dataverse table entry inside power.config.json
164
- * `databaseReferences[*].dataSources[*]`.
165
- */
166
-
167
- /**
168
- * Shape of a single database reference entry inside power.config.json
169
- * `databaseReferences`.
170
- */
171
-
172
- /**
173
- * Build the CDS data source config array that the SDK reads via
174
- * AppPowerAppsClientPlugin.getAppCdsDataSourceConfigsAsync.
175
- *
176
- * Pass the resolved Dataverse org URL (from useConnectionRefs) and the
177
- * databaseReferences map from power.config.json. The resulting array is
178
- * passed directly to <PowerAppsHostProvider dataSources={...} />.
179
- *
180
- * @param databaseReferences - The `databaseReferences` map from power.config.json.
181
- * @param dataverseOrgUrl - The resolved Dataverse org URL
182
- * (connectionRefs['commondataserviceforapps']?.datasetName).
183
- * Pass an empty string before the org URL is known;
184
- * the host provider will receive empty runtimeUrl entries
185
- * and the SDK will not attempt Dataverse calls.
186
- */
187
- function buildCdsDataSources(databaseReferences, dataverseOrgUrl) {
188
- return Object.values(databaseReferences).flatMap(dbRef => Object.values(dbRef.dataSources ?? {}).map(ds => ({
189
- runtimeUrl: dataverseOrgUrl ? `${dataverseOrgUrl}/api/data/v9.0/` : '',
190
- entitySetName: ds.entitySetName ?? '',
191
- logicalName: ds.logicalName ?? '',
192
- version: '9.2'
193
- })));
90
+ /** Delegated permission required by the connection user-consent endpoint. */
91
+ function connectionUserConsentScope(region) {
92
+ const host = HOST_NAME_SUFFIX[region] ?? HOST_NAME_SUFFIX['prod'];
93
+ return `https://${host}/Connectivity.Connections.UserConsent`;
194
94
  }
@@ -9,9 +9,14 @@ var _reactNative = require("react-native");
9
9
  var _reactNativeSafeAreaContext = require("react-native-safe-area-context");
10
10
  var _setupNativeHost = require("../bridge/setupNativeHost.js");
11
11
  var _NativePowerAppsBridge = require("../bridge/NativePowerAppsBridge.js");
12
- var _useConnectionRefs = require("../platform/useConnectionRefs.js");
12
+ var _useConnectionRefs = require("../connections/useConnectionRefs.js");
13
+ var _DataverseDataSources = require("../platform/DataverseDataSources.js");
13
14
  var _PowerPlatformEnvironmentApi = require("../platform/PowerPlatformEnvironmentApi.js");
14
15
  var _ConnectionSetupScreen = require("../connections/ConnectionSetupScreen.js");
16
+ var _types = require("../connections/types.js");
17
+ var _PowerPlatformConnectionApi = require("../connections/PowerPlatformConnectionApi.js");
18
+ var _connectionState = require("../connections/connectionState.js");
19
+ var _isDevPlayer = require("../utils/isDevPlayer.js");
15
20
  var _logger = require("../logger.js");
16
21
  var _index = require("../telemetry/index.js");
17
22
  var _jsxRuntime = require("react/jsx-runtime");
@@ -48,6 +53,7 @@ function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r
48
53
  // Entra ID token scope for the APIM connector proxy (API Hub).
49
54
  const CONNECTOR_SCOPE = 'https://apihub.azure.com/.default';
50
55
  const EMPTY_DATABASE_REFERENCES = {};
56
+ const EMPTY_CONNECTION_REFERENCES = {};
51
57
  /**
52
58
  * Initialises the NativePowerAppsBridge and manages the full pre-launch
53
59
  * pipeline — connection resolution, disambiguation, and creation — before
@@ -58,6 +64,7 @@ const EMPTY_DATABASE_REFERENCES = {};
58
64
  function PowerAppsHostProvider({
59
65
  acquireToken,
60
66
  isSignedIn,
67
+ onSignOut,
61
68
  appId,
62
69
  environmentId,
63
70
  region = 'prod',
@@ -67,18 +74,75 @@ function PowerAppsHostProvider({
67
74
  renderOAuthWebView,
68
75
  isvClientId,
69
76
  onDisambiguationNeeded,
77
+ onConnectionConsent,
78
+ onResourceConsent,
79
+ onConnectionConsentDeclined,
70
80
  children
71
81
  }) {
72
82
  // Extra refs produced by ConnectionSetupScreen for newly-created connections.
73
83
  const [extraConnectionRefs, setExtraConnectionRefs] = (0, _react.useState)({});
74
-
75
- // setupDone is derived — true as soon as extraConnectionRefs is populated.
76
- const setupDone = Object.keys(extraConnectionRefs).length > 0;
84
+ const [consentApproved, setConsentApproved] = (0, _react.useState)(false);
85
+ const setupDone = consentApproved;
86
+ const renderConnectionError = (title, messages, useNeutralText = false) => /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
87
+ style: {
88
+ flex: 1,
89
+ padding: 24,
90
+ justifyContent: 'center',
91
+ backgroundColor: '#F8F8F8'
92
+ },
93
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
94
+ style: {
95
+ fontSize: 20,
96
+ fontWeight: '700',
97
+ color: '#1A1A1A',
98
+ marginBottom: 12
99
+ },
100
+ children: title
101
+ }), messages.map(message => /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
102
+ style: {
103
+ fontSize: 14,
104
+ color: useNeutralText ? '#323130' : '#C4262E',
105
+ lineHeight: 20,
106
+ marginBottom: 8
107
+ },
108
+ children: message
109
+ }, message)), isSignedIn && onSignOut ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, {
110
+ accessibilityRole: "button",
111
+ onPress: () => {
112
+ void onSignOut();
113
+ },
114
+ style: {
115
+ alignSelf: 'flex-start',
116
+ backgroundColor: '#0F6CBD',
117
+ paddingHorizontal: 20,
118
+ paddingVertical: 12,
119
+ borderRadius: 4,
120
+ marginTop: 12
121
+ },
122
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
123
+ style: {
124
+ color: '#FFFFFF',
125
+ fontSize: 15,
126
+ fontWeight: '600'
127
+ },
128
+ children: "Sign out"
129
+ })
130
+ }) : null]
131
+ });
77
132
  const {
78
133
  connectionRefs,
79
134
  isReady,
135
+ error: connectionError,
136
+ errorKind: connectionErrorKind,
80
137
  missingConnectors,
81
- dataverseOrgUrl
138
+ missingConnectionReferences,
139
+ connectionRequirements,
140
+ resourceConsents: launchResourceConsents,
141
+ configurationErrors,
142
+ requiresConnectionConsent,
143
+ resolvedConfigKey,
144
+ dataverseOrgUrl,
145
+ databaseReferences: resolvedDatabaseReferences
82
146
  } = (0, _useConnectionRefs.useConnectionRefs)({
83
147
  isSignedIn,
84
148
  acquireToken,
@@ -96,39 +160,43 @@ function PowerAppsHostProvider({
96
160
  if (!isSignedIn) {
97
161
  (0, _logger.debugLog)('[PAHost] user signed out — resetting bridge state');
98
162
  setExtraConnectionRefs({});
163
+ setConsentApproved(false);
99
164
  }
100
165
  }, [isSignedIn]);
166
+ (0, _react.useEffect)(() => {
167
+ setExtraConnectionRefs({});
168
+ setConsentApproved(false);
169
+ }, [appId, environmentId, connectionReferences]);
101
170
 
102
171
  // Merge hook refs with anything the setup screen created.
103
172
  const mergedRefs = (0, _react.useMemo)(() => ({
104
173
  ...connectionRefs,
105
174
  ...extraConnectionRefs
106
175
  }), [connectionRefs, extraConnectionRefs]);
107
- const staticConnectionRefs = (0, _react.useMemo)(() => (0, _NativePowerAppsBridge.buildConnectionReferences)(connectionReferences), [connectionReferences]);
176
+ const staticConnectionRefs = (0, _react.useMemo)(() => _isDevPlayer.isDevPlayer ? (0, _NativePowerAppsBridge.buildConnectionReferences)(connectionReferences) : EMPTY_CONNECTION_REFERENCES, [connectionReferences, _isDevPlayer.isDevPlayer]);
108
177
 
109
178
  // dataverseOrgUrl comes directly from useConnectionRefs (fetched from the
110
179
  // environment metadata regardless of whether a commondataserviceforapps
111
180
  // connection reference is configured). This allows Dataverse calls to work
112
181
  // even when there is no explicit Dataverse connection in connectionReferences.
113
- const cdsDataSources = (0, _react.useMemo)(() => (0, _PowerPlatformEnvironmentApi.buildCdsDataSources)(databaseReferences ?? EMPTY_DATABASE_REFERENCES, dataverseOrgUrl), [databaseReferences, dataverseOrgUrl]);
182
+ const cdsDataSources = (0, _react.useMemo)(() => (0, _DataverseDataSources.buildCdsDataSources)(_isDevPlayer.isDevPlayer ? databaseReferences ?? EMPTY_DATABASE_REFERENCES : resolvedDatabaseReferences, dataverseOrgUrl), [databaseReferences, resolvedDatabaseReferences, dataverseOrgUrl, _isDevPlayer.isDevPlayer]);
114
183
 
115
184
  // Build the missing-connector entries for ConnectionSetupScreen.
116
- const missingEntries = (0, _react.useMemo)(() => missingConnectors.map(apiId => {
117
- const localRefId = Object.keys(connectionReferences).find(k => connectionReferences[k].id === apiId) ?? apiId;
118
- return {
119
- apiId,
120
- localRefId
121
- };
122
- }), [missingConnectors, connectionReferences]);
185
+ const missingEntries = (0, _react.useMemo)(() => missingConnectionReferences, [missingConnectionReferences]);
186
+ const resourceConsents = (0, _react.useMemo)(() => [...new Map(launchResourceConsents.map(resource => [resource.resourceId.toLowerCase(), resource])).values()], [launchResourceConsents]);
187
+ const currentConfigKey = (0, _react.useMemo)(() => (0, _types.buildConnectionResolutionKey)(appId, environmentId, _isDevPlayer.isDevPlayer ? connectionReferences : {}), [appId, environmentId, connectionReferences, _isDevPlayer.isDevPlayer]);
188
+ const resolutionMatchesCurrentConfig = resolvedConfigKey === currentConfigKey;
189
+ const needsConsent = (0, _connectionState.shouldShowConnectionReview)(_isDevPlayer.isDevPlayer, requiresConnectionConsent, resourceConsents.length > 0);
123
190
 
124
191
  // Called by ConnectionSetupScreen once every missing connector is resolved.
125
- const handleSetupComplete = (0, _react.useCallback)(resolvedIds => {
192
+ const handleSetupComplete = (0, _react.useCallback)(async resolvedIds => {
126
193
  const refs = {};
127
- for (const [apiId, connectionId] of Object.entries(resolvedIds)) {
128
- const localRefId = Object.keys(connectionReferences).find(k => connectionReferences[k].id === apiId);
129
- if (!localRefId) continue;
130
- const dataSources = connectionReferences[localRefId].dataSources;
131
- const runtimeUrl = dataSources.map(ds => schemaMap[ds]?.properties?.primaryRuntimeUrl).find(Boolean) ?? '';
194
+ for (const [localRefId, connectionId] of Object.entries(resolvedIds)) {
195
+ const requirement = connectionRequirements.find(item => item.localRefId === localRefId);
196
+ if (!requirement) continue;
197
+ const apiId = requirement.apiId;
198
+ const dataSources = requirement.dataSources;
199
+ const runtimeUrl = dataSources.map(ds => connectionRefsRef.current[ds]?.runtimeUrl ?? (_isDevPlayer.isDevPlayer ? schemaMap[ds]?.properties?.primaryRuntimeUrl : undefined)).find(Boolean) ?? '';
132
200
  for (const ds of dataSources) {
133
201
  refs[ds] = {
134
202
  apiId,
@@ -142,7 +210,44 @@ function PowerAppsHostProvider({
142
210
  }
143
211
  }
144
212
  setExtraConnectionRefs(refs);
145
- }, [connectionReferences, schemaMap]);
213
+ const mergedForConsent = {
214
+ ...connectionRefsRef.current,
215
+ ...refs
216
+ };
217
+ const usages = connectionRequirements.map(requirement => ({
218
+ localRefId: requirement.localRefId,
219
+ apiId: requirement.apiId,
220
+ connectionId: requirement.dataSources.map(dataSource => mergedForConsent[dataSource]?.connectionName).find(Boolean) ?? '',
221
+ dataSources: requirement.dataSources,
222
+ appActions: requirement.appActions ?? [],
223
+ apiScopes: requirement.apiScopes
224
+ }));
225
+ if (usages.some(usage => !usage.connectionId)) {
226
+ throw new Error('Not all required connections have been resolved.');
227
+ }
228
+ if (!_isDevPlayer.isDevPlayer && resourceConsents.length > 0 && !onResourceConsent) {
229
+ throw new Error('Protected resource consent requires an onResourceConsent persistence callback.');
230
+ }
231
+ const shouldPersistUsages = (0, _connectionState.shouldPersistConnectionUsages)(_isDevPlayer.isDevPlayer, appId);
232
+ if (shouldPersistUsages && appId && usages.length > 0) {
233
+ const token = await acquireToken([(0, _PowerPlatformEnvironmentApi.powerPlatformScope)(region)]);
234
+ if (!token) throw new Error('Unable to acquire a token to register connection usage.');
235
+ await Promise.all(usages.map(usage => (0, _PowerPlatformConnectionApi.postConnectionUsage)(appId, usage, environmentId, region, token)));
236
+ await (0, _PowerPlatformConnectionApi.reportScopeConsents)(appId, usages, environmentId, region, token);
237
+ }
238
+ if (!_isDevPlayer.isDevPlayer) {
239
+ await onConnectionConsent?.(usages);
240
+ await onResourceConsent?.(resourceConsents);
241
+ }
242
+ setConsentApproved(true);
243
+ }, [acquireToken, appId, environmentId, region, schemaMap, connectionRequirements, onConnectionConsent, onResourceConsent, resourceConsents, _isDevPlayer.isDevPlayer]);
244
+ const consentDetails = (0, _react.useMemo)(() => connectionRequirements.map(requirement => ({
245
+ localRefId: requirement.localRefId,
246
+ connectorId: requirement.connectorId,
247
+ displayName: requirement.displayName ?? requirement.connectorId.replace(/^shared_/, ''),
248
+ description: `Uses ${requirement.dataSources.join(', ')}`,
249
+ appActions: requirement.appActions ?? []
250
+ })), [connectionRequirements]);
146
251
 
147
252
  // ---- Bridge wiring ----
148
253
 
@@ -154,7 +259,7 @@ function PowerAppsHostProvider({
154
259
  connectionRefsRef.current = connectionRefs;
155
260
  const configuredBridgeInputsRef = (0, _react.useRef)(undefined);
156
261
  const [, setBridgeRegistrationVersion] = (0, _react.useState)(0);
157
- const hasResolvedConnections = isSignedIn && isReady && (missingConnectors.length === 0 || setupDone);
262
+ const hasResolvedConnections = isSignedIn && isReady && resolutionMatchesCurrentConfig && (missingConnectors.length === 0 || consentApproved) && (!needsConsent || consentApproved);
158
263
  const isBridgeConfiguredFor = (connections, dataSources) => {
159
264
  const currentInputs = configuredBridgeInputsRef.current;
160
265
  return currentInputs?.connections === connections && currentInputs.dataSources === dataSources;
@@ -254,13 +359,34 @@ function PowerAppsHostProvider({
254
359
  }
255
360
  }, [isSignedIn, isReady]);
256
361
  (0, _react.useEffect)(() => {
257
- if (isSignedIn && isReady && missingConnectors.length > 0 && !setupDone) {
362
+ if (isSignedIn && isReady && (missingConnectors.length > 0 || needsConsent) && !setupDone) {
258
363
  (0, _logger.debugLog)(`[PAHost] render: showing ConnectionSetupScreen for ${missingConnectors.length} missing connector(s):`, missingConnectors.join(', '));
259
364
  }
260
- }, [isSignedIn, isReady, missingConnectors, setupDone]);
365
+ }, [isSignedIn, isReady, missingConnectors, needsConsent, setupDone]);
261
366
 
262
367
  // ---- Render ----
263
368
 
369
+ if (connectionError) {
370
+ const accessDenied = connectionErrorKind === 'app-access-denied';
371
+ return renderConnectionError(accessDenied ? 'You don’t have access' : 'Unable to load app', [connectionError], accessDenied);
372
+ }
373
+ if (configurationErrors.length > 0) {
374
+ return renderConnectionError('Connection configuration error', configurationErrors);
375
+ }
376
+ if (isSignedIn && !resolutionMatchesCurrentConfig) {
377
+ return /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
378
+ style: {
379
+ flex: 1,
380
+ alignItems: 'center',
381
+ justifyContent: 'center',
382
+ backgroundColor: '#F8F8F8'
383
+ },
384
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.ActivityIndicator, {
385
+ size: "large"
386
+ })
387
+ });
388
+ }
389
+
264
390
  // First mount: register the native bridge with static manifest refs before
265
391
  // any route children can mount and call into @microsoft/power-apps.
266
392
  if (!staticBridgeRegistered && !hasResolvedConnections) {
@@ -296,7 +422,7 @@ function PowerAppsHostProvider({
296
422
  // Wrapped in SafeAreaProvider so useSafeAreaInsets() works even when the
297
423
  // consuming app hasn't mounted its own SafeAreaProvider yet. Nesting is
298
424
  // safe — react-native-safe-area-context treats inner providers as no-ops.
299
- if (isSignedIn && isReady && missingConnectors.length > 0 && !setupDone) {
425
+ if (isSignedIn && isReady && (missingConnectors.length > 0 || needsConsent) && !setupDone) {
300
426
  return /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNativeSafeAreaContext.SafeAreaProvider, {
301
427
  children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_ConnectionSetupScreen.ConnectionSetupScreen, {
302
428
  missingConnectors: missingEntries,
@@ -304,8 +430,13 @@ function PowerAppsHostProvider({
304
430
  environmentId: environmentId,
305
431
  region: region,
306
432
  acquireToken: acquireToken,
433
+ onSignOut: isSignedIn ? onSignOut : undefined,
307
434
  isvClientId: isvClientId,
308
435
  onAllConnected: handleSetupComplete,
436
+ consentDetails: consentDetails,
437
+ resourceConsents: resourceConsents,
438
+ onConsentDeclined: onConnectionConsentDeclined,
439
+ showReview: !_isDevPlayer.isDevPlayer,
309
440
  renderOAuthWebView: renderOAuthWebView
310
441
  // No onSkip — connections are required.
311
442
  })