@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
@@ -20,10 +20,12 @@
20
20
  * // Call setup.checkNow() to re-poll after WebView finishes
21
21
  */
22
22
 
23
- import { useState, useCallback, useRef } from 'react';
24
- import { createConnection, pollConnectionStatus, confirmConsentCode, addAadAppToConnectionAcl, parseJwtOid, SERVICE_POWERAPPS_SCOPE } from "../platform/PowerPlatformConnectionApi.js";
25
- import { powerPlatformScope } from "../platform/PowerPlatformEnvironmentApi.js";
23
+ import { useState, useCallback, useEffect, useRef } from 'react';
24
+ import { createConnection, repairConnection, pollConnectionStatus, confirmConsentCode, grantConnectionUserConsent } from "./PowerPlatformConnectionApi.js";
25
+ import { connectionUserConsentScope, powerPlatformScope } from "../platform/PowerPlatformEnvironmentApi.js";
26
26
  import { extractConnectionId } from "../connections/disambiguate.js";
27
+ import { shouldGrantConnectionUserConsent } from "./connectionState.js";
28
+ import { isDevPlayer } from "../utils/isDevPlayer.js";
27
29
  import { debugWarn } from "../logger.js";
28
30
  import { getAppLogger, HostOperationNames } from "../telemetry/index.js";
29
31
 
@@ -52,7 +54,7 @@ export function useConnectionSetup({
52
54
  region = 'prod',
53
55
  acquireToken,
54
56
  onConnected,
55
- isvClientId,
57
+ sharedConnectionId,
56
58
  oauthRedirectUrl,
57
59
  polling
58
60
  }) {
@@ -64,44 +66,53 @@ export function useConnectionSetup({
64
66
  const connectionNameRef = useRef(null);
65
67
  // Prevent concurrent startCreate / checkNow calls.
66
68
  const inFlightRef = useRef(false);
69
+ const operationRef = useRef(null);
70
+ const beginOperation = useCallback(() => {
71
+ operationRef.current?.abort();
72
+ const controller = new AbortController();
73
+ operationRef.current = controller;
74
+ return controller;
75
+ }, []);
76
+ useEffect(() => () => operationRef.current?.abort(), []);
67
77
  const handleConnected = useCallback(async instance => {
68
78
  setPhase('connected');
69
79
  setConsentUrl(null);
80
+ if (!shouldGrantConnectionUserConsent({
81
+ sharedConnectionId
82
+ }, isDevPlayer)) {
83
+ onConnected?.(instance);
84
+ return;
85
+ }
70
86
 
71
- // The RP auto-writes the user as Owner when a connection is created.
72
- // However, APIM also checks that the requesting appid is present in the
73
- // ACE's AppIds list. For third-party ISV tokens this is never populated
74
- // automatically — we must explicitly add the ISV's client ID to the
75
- // connection ACL so that APIM accepts tokens bearing that appid at runtime.
76
- if (isvClientId) {
77
- const paToken = await acquireToken([SERVICE_POWERAPPS_SCOPE]);
78
- const userOid = paToken ? parseJwtOid(paToken) : null;
79
- if (!paToken || !userOid) {
80
- setPhase('error');
81
- setErrorMessage('Connection was created, but access could not be granted to this app. Please try again.');
82
- debugWarn('[useConnectionSetup] could not grant connection ACL: missing Power Apps token or user OID');
83
- getAppLogger().trackErrorEvent(HostOperationNames.PlatformConnectionAclGrant, {
84
- phase: 'setupConnectionAcl',
85
- reason: !paToken ? 'missingPowerAppsToken' : 'missingUserOid'
86
- });
87
- return;
88
- }
89
- const aclGranted = await addAadAppToConnectionAcl(connectorId, instance.id, environmentId, isvClientId, userOid, paToken);
90
- if (!aclGranted) {
91
- setPhase('error');
92
- setErrorMessage('Connection was created, but access could not be granted to this app. Please try again.');
93
- getAppLogger().trackErrorEvent(HostOperationNames.PlatformConnectionAclGrant, {
94
- phase: 'setupConnectionAcl',
95
- reason: 'aclGrantRejected'
96
- });
97
- return;
98
- }
87
+ // APIM checks the requesting appid against the user's effective ACL.
88
+ // User consent lets RP select the applicable direct, group, or tenant ACL.
89
+ const consentToken = await acquireToken([connectionUserConsentScope(region)]);
90
+ if (!consentToken) {
91
+ setPhase('error');
92
+ setErrorMessage('Connection was created, but access could not be granted to this app. Please try again.');
93
+ debugWarn('[useConnectionSetup] could not grant connection consent: missing Power Platform token');
94
+ getAppLogger().trackErrorEvent(HostOperationNames.PlatformConnectionAclGrant, {
95
+ phase: 'setupConnectionAcl',
96
+ reason: 'missingPowerPlatformToken'
97
+ });
98
+ return;
99
+ }
100
+ const aclGranted = await grantConnectionUserConsent(connectorId, instance.id, environmentId, region, consentToken);
101
+ if (!aclGranted) {
102
+ setPhase('error');
103
+ setErrorMessage('Connection was created, but access could not be granted to this app. Please try again.');
104
+ getAppLogger().trackErrorEvent(HostOperationNames.PlatformConnectionAclGrant, {
105
+ phase: 'setupConnectionAcl',
106
+ reason: 'aclGrantRejected'
107
+ });
108
+ return;
99
109
  }
100
110
  onConnected?.(instance);
101
- }, [onConnected, acquireToken, connectorId, environmentId, isvClientId]);
102
- const startCreate = useCallback(async () => {
111
+ }, [onConnected, acquireToken, connectorId, environmentId, region, sharedConnectionId]);
112
+ const startCreate = useCallback(async input => {
103
113
  if (inFlightRef.current) return;
104
114
  inFlightRef.current = true;
115
+ const operation = beginOperation();
105
116
  setPhase('creating');
106
117
  setErrorMessage(null);
107
118
  setConsentUrl(null);
@@ -113,7 +124,7 @@ export function useConnectionSetup({
113
124
  setErrorMessage('Failed to acquire authentication token');
114
125
  return;
115
126
  }
116
- const result = await createConnection(connectorId, environmentId, region, token, undefined, oauthRedirectUrl);
127
+ const result = await createConnection(connectorId, environmentId, region, token, undefined, oauthRedirectUrl, input);
117
128
  if (result.status === 'error') {
118
129
  setPhase('error');
119
130
  setErrorMessage(result.message);
@@ -124,10 +135,11 @@ export function useConnectionSetup({
124
135
  // Connection is already Connected — poll once to get the full instance.
125
136
  const instance = await pollConnectionStatus(connectorId, result.connectionName, environmentId, region, acquireToken, {
126
137
  timeoutMs: 10_000,
127
- ...polling
138
+ ...polling,
139
+ signal: operation.signal
128
140
  });
129
141
  if (instance) {
130
- handleConnected(instance);
142
+ await handleConnected(instance);
131
143
  } else {
132
144
  // Already created but not Connected yet — treat as oauth phase without a URL.
133
145
  setPhase('oauth');
@@ -138,36 +150,77 @@ export function useConnectionSetup({
138
150
  // status === 'pending': show the OAuth URL in a WebView.
139
151
  setConsentUrl(result.consentLink);
140
152
  setPhase('oauth');
141
-
142
- // Start background polling immediately — the user may finish OAuth quickly.
143
- const instance = await pollConnectionStatus(connectorId, result.connectionName, environmentId, region, acquireToken, polling);
144
-
145
- // If the phase changed while we were polling (e.g., user dismissed WebView
146
- // and checkNow already resolved it), don't override.
147
- if (instance && phase !== 'connected') {
148
- handleConnected(instance);
149
- } else if (!instance && phase !== 'connected') {
153
+ } finally {
154
+ inFlightRef.current = false;
155
+ }
156
+ }, [connectorId, environmentId, region, acquireToken, polling, handleConnected, beginOperation]);
157
+ const startRepair = useCallback(async (instance, input, requiresApiConsent = true) => {
158
+ if (inFlightRef.current) return;
159
+ inFlightRef.current = true;
160
+ const operation = beginOperation();
161
+ setPhase('creating');
162
+ setErrorMessage(null);
163
+ setConsentUrl(null);
164
+ connectionNameRef.current = instance.name;
165
+ try {
166
+ const token = await acquireToken([powerPlatformScope(region)]);
167
+ if (!token) {
168
+ setPhase('error');
169
+ setErrorMessage('Failed to acquire authentication token');
170
+ return;
171
+ }
172
+ const result = await repairConnection(connectorId, instance.name, environmentId, region, token, oauthRedirectUrl, input, requiresApiConsent);
173
+ if (result.status === 'error') {
150
174
  setPhase('error');
151
- setErrorMessage('Connection setup timed out. Please try again.');
175
+ setErrorMessage(result.message);
176
+ return;
177
+ }
178
+ connectionNameRef.current = result.connectionName;
179
+ if (result.status === 'pending') {
180
+ setConsentUrl(result.consentLink);
181
+ setPhase('oauth');
182
+ } else {
183
+ const repaired = await pollConnectionStatus(connectorId, result.connectionName, environmentId, region, acquireToken, {
184
+ timeoutMs: 10_000,
185
+ ...polling,
186
+ signal: operation.signal
187
+ });
188
+ if (repaired) await handleConnected(repaired);else {
189
+ setPhase('error');
190
+ setErrorMessage('The connection was updated but did not become active.');
191
+ }
152
192
  }
153
193
  } finally {
154
194
  inFlightRef.current = false;
155
195
  }
156
- }, [connectorId, environmentId, region, acquireToken, polling, handleConnected, phase]);
196
+ }, [acquireToken, beginOperation, connectorId, environmentId, handleConnected, oauthRedirectUrl, polling, region]);
197
+ const useExisting = useCallback(async instance => {
198
+ if (inFlightRef.current) return;
199
+ inFlightRef.current = true;
200
+ setPhase('confirming');
201
+ setErrorMessage(null);
202
+ try {
203
+ await handleConnected(instance);
204
+ } finally {
205
+ inFlightRef.current = false;
206
+ }
207
+ }, [handleConnected]);
157
208
  const checkNow = useCallback(async () => {
158
209
  const name = connectionNameRef.current;
159
210
  if (!name || inFlightRef.current) return;
160
211
  if (phase !== 'oauth' && phase !== 'confirming') return;
161
212
  inFlightRef.current = true;
213
+ const operation = beginOperation();
162
214
  setPhase('confirming');
163
215
  try {
164
216
  const instance = await pollConnectionStatus(connectorId, name, environmentId, region, acquireToken, {
165
217
  intervalMs: 1000,
166
218
  timeoutMs: 30_000,
167
- ...polling
219
+ ...polling,
220
+ signal: operation.signal
168
221
  });
169
222
  if (instance) {
170
- handleConnected(instance);
223
+ await handleConnected(instance);
171
224
  } else {
172
225
  // Not yet connected — go back to oauth so the user can retry.
173
226
  setPhase('oauth');
@@ -176,8 +229,10 @@ export function useConnectionSetup({
176
229
  } finally {
177
230
  inFlightRef.current = false;
178
231
  }
179
- }, [phase, environmentId, region, acquireToken, polling, handleConnected]);
232
+ }, [phase, connectorId, environmentId, region, acquireToken, polling, handleConnected, beginOperation]);
180
233
  const reset = useCallback(() => {
234
+ operationRef.current?.abort();
235
+ operationRef.current = null;
181
236
  inFlightRef.current = false;
182
237
  connectionNameRef.current = null;
183
238
  setPhase('idle');
@@ -197,6 +252,7 @@ export function useConnectionSetup({
197
252
  const name = connectionNameRef.current;
198
253
  if (!name || inFlightRef.current) return;
199
254
  inFlightRef.current = true;
255
+ const operation = beginOperation();
200
256
  setPhase('confirming');
201
257
  try {
202
258
  if (code) {
@@ -209,10 +265,11 @@ export function useConnectionSetup({
209
265
  const instance = await pollConnectionStatus(connectorId, name, environmentId, region, acquireToken, {
210
266
  intervalMs: 1000,
211
267
  timeoutMs: 30_000,
212
- ...polling
268
+ ...polling,
269
+ signal: operation.signal
213
270
  });
214
271
  if (instance) {
215
- handleConnected(instance);
272
+ await handleConnected(instance);
216
273
  } else {
217
274
  setPhase('oauth');
218
275
  setErrorMessage('Connection is not yet active. Complete the sign-in and try again.');
@@ -220,12 +277,14 @@ export function useConnectionSetup({
220
277
  } finally {
221
278
  inFlightRef.current = false;
222
279
  }
223
- }, [connectorId, environmentId, region, acquireToken, polling, handleConnected]);
280
+ }, [connectorId, environmentId, region, acquireToken, polling, handleConnected, beginOperation]);
224
281
  return {
225
282
  phase,
226
283
  consentUrl,
227
284
  errorMessage,
228
285
  startCreate,
286
+ startRepair,
287
+ useExisting,
229
288
  checkNow,
230
289
  completeConsent,
231
290
  reset
@@ -19,6 +19,9 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
19
19
  */
20
20
  export function FilePicker({
21
21
  label,
22
+ testID,
23
+ fileRowTestID,
24
+ pickButtonTestID,
22
25
  fileName,
23
26
  onDownload,
24
27
  shareTitle,
@@ -85,6 +88,7 @@ export function FilePicker({
85
88
  }
86
89
  };
87
90
  return /*#__PURE__*/_jsxs(View, {
91
+ testID: testID,
88
92
  style: [styles.container, containerStyle],
89
93
  children: [label ? /*#__PURE__*/_jsx(Text, {
90
94
  style: [styles.label, {
@@ -92,6 +96,7 @@ export function FilePicker({
92
96
  }, labelStyle],
93
97
  children: label
94
98
  }) : null, displayName ? /*#__PURE__*/_jsxs(TouchableOpacity, {
99
+ testID: fileRowTestID,
95
100
  style: [styles.fileRow, fileRowStyle],
96
101
  onPress: onDownload ? download : undefined,
97
102
  activeOpacity: onDownload ? 0.7 : 1,
@@ -115,6 +120,7 @@ export function FilePicker({
115
120
  color: accent
116
121
  }))]
117
122
  }) : null, onChange ? /*#__PURE__*/_jsxs(TouchableOpacity, {
123
+ testID: pickButtonTestID,
118
124
  style: [styles.button, {
119
125
  borderColor: accent,
120
126
  backgroundColor: bg
@@ -17,6 +17,10 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
17
17
  */
18
18
  export function ImagePicker({
19
19
  label = 'Image',
20
+ testID,
21
+ cameraButtonTestID,
22
+ selectButtonTestID,
23
+ previewTestID,
20
24
  initialBase64,
21
25
  onChange,
22
26
  containerStyle,
@@ -131,6 +135,7 @@ export function ImagePicker({
131
135
  }
132
136
  };
133
137
  return /*#__PURE__*/_jsxs(View, {
138
+ testID: testID,
134
139
  style: [styles.container, containerStyle],
135
140
  children: [label ? /*#__PURE__*/_jsx(Text, {
136
141
  style: [styles.label, {
@@ -140,6 +145,7 @@ export function ImagePicker({
140
145
  }) : null, /*#__PURE__*/_jsxs(View, {
141
146
  style: [styles.buttonRow, buttonRowStyle],
142
147
  children: [/*#__PURE__*/_jsxs(TouchableOpacity, {
148
+ testID: cameraButtonTestID,
143
149
  style: [styles.button, {
144
150
  borderColor: accentColor_resolved,
145
151
  backgroundColor: bg
@@ -161,6 +167,7 @@ export function ImagePicker({
161
167
  children: reading ? readingLabel : cameraButtonLabel
162
168
  })]
163
169
  }), /*#__PURE__*/_jsxs(TouchableOpacity, {
170
+ testID: selectButtonTestID,
164
171
  style: [styles.button, {
165
172
  borderColor: accentColor_resolved,
166
173
  backgroundColor: bg
@@ -183,6 +190,7 @@ export function ImagePicker({
183
190
  })]
184
191
  })]
185
192
  }), previewUri ? /*#__PURE__*/_jsx(Image, {
193
+ testID: previewTestID,
186
194
  source: {
187
195
  uri: previewUri
188
196
  },
@@ -6,20 +6,30 @@
6
6
  *
7
7
  */
8
8
 
9
+ // Side-effect: installs the wrap-injected customer asset resolver before any
10
+ // screen renders. No-op on web/dev and in any base app without the native
11
+ // module, so importing the package root is always safe.
12
+ import '@microsoft/power-apps-native-assets';
9
13
  export { NativePowerAppsBridge } from "./bridge/NativePowerAppsBridge.js";
10
14
  export { PowerPagesBridge, setupPowerPagesHost } from "./bridge/PowerPagesBridge.js";
11
15
  export { buildConnectionReferences } from "./bridge/NativePowerAppsBridge.js";
12
16
  export { setupNativeHost } from "./bridge/setupNativeHost.js";
17
+ export { updateBridgeOptionsInPlace } from "./bridge/bridgeState.js";
13
18
  export { AuthProvider, useAuth } from './auth/AuthContext';
14
19
  export { completePowerAppsAuthSession } from "./auth/completePowerAppsAuthSession.js";
15
20
  export { PowerAppsHostProvider } from './provider/PowerAppsHostProvider';
16
21
  export { PowerAppsProvider } from "./provider/PowerAppsProvider.js";
17
- export { fetchInstanceUrl, buildCdsDataSources, buildEnvironmentEndpoint, POWER_PLATFORM_API_SCOPE } from "./platform/PowerPlatformEnvironmentApi.js";
18
- export { useConnectionRefs } from "./platform/useConnectionRefs.js";
22
+ export { buildEnvironmentEndpoint, POWER_PLATFORM_API_SCOPE } from "./platform/PowerPlatformEnvironmentApi.js";
23
+ export { fetchInstanceUrl } from "./platform/DataverseGlobalDiscoveryApi.js";
24
+ export { buildCdsDataSources } from "./platform/DataverseDataSources.js";
25
+ export { useConnectionRefs } from "./connections/useConnectionRefs.js";
19
26
  // Connection creation & OAuth flow
20
- export { fetchConnectorMetadata, fetchConnectionsForConnector, createConnection, pollConnectionStatus, confirmConsentCode, fetchAppLaunchData } from "./platform/PowerPlatformConnectionApi.js";
27
+ export { fetchConnectorMetadata, fetchConnectionsForConnector, deleteConnection, createConnection, pollConnectionStatus, confirmConsentCode, repairConnection, fetchAppLaunchData, fetchAppConnectionUsages, postConnectionUsage, reportScopeConsents } from "./connections/PowerPlatformConnectionApi.js";
21
28
  // Connection disambiguation
22
29
  export { disambiguateConnections, extractConnectionId } from "./connections/disambiguate.js";
30
+ export { shouldPersistConnectionUsages, shouldShowConnectionReview } from "./connections/connectionState.js";
31
+ export { buildConnectionRequirements, buildConnectionResolutionKey, findConnectionDataSourceConflicts, hasCompleteStaticConnectionConfig, isCompositeConnectionApiId, getConnectorId, normalizeApiId } from "./connections/types.js";
32
+ export { buildConnectionUserConsentUrl, buildConsentLinkDefinition, buildConnectionParameterSets, buildConnectionUsageRequest, buildConnectionUsageUrl, buildScopeConsentRequest, buildScopeConsentUrl, hasOAuthParameter, parseConnectionParameters, serializeConnectionParameters } from "./connections/connectionMetadata.js";
23
33
  // Connection setup hook
24
34
  export { useConnectionSetup } from "./connections/useConnectionSetup.js";
25
35
  // Ready-made connection setup screen
@@ -38,6 +48,6 @@ export { isDevPlayer } from "./utils/isDevPlayer.js";
38
48
  export { base64ToDataverseUploadFile, base64ToUint8Array, createDataverseUploadFile } from "./utils/dataverseUtils.js";
39
49
 
40
50
  // ── Telemetry ─────────────────────────────────────────────────────────────────
41
- export { getAppLogger, configureAppLogger, HostOperationNames } from "./telemetry/index.js";
51
+ export { getAppLogger, configureAppLogger, getCustomerTelemetryLogger, HostOperationNames } from "./telemetry/index.js";
42
52
  // ── Error boundary ────────────────────────────────────────────────────────────
43
53
  export { ErrorBoundary } from "./ErrorBoundary.js";
@@ -3,20 +3,22 @@
3
3
  /**
4
4
  * Conditional debug logger for power-apps-native-host.
5
5
  *
6
- * Debug logs (debugLog / debugWarn) are only emitted when the JS bundle
7
- * is running inside the Power Apps Dev Player (detected via
8
- * `Constants.expoConfig.extra.isDevPlayer`). They are silenced in release
9
- * APKs generated from the template so the production log stream stays clean.
6
+ * Debug logs (debugLog / debugWarn) are emitted in development bundles or when
7
+ * running inside Power Apps Dev Player. They remain silent in wrapped release
8
+ * apps so the production log stream stays clean.
10
9
  */
11
10
 
12
11
  import { isDevPlayer } from "./utils/isDevPlayer.js";
12
+ export function isDebugLoggingEnabled() {
13
+ return __DEV__ || isDevPlayer;
14
+ }
13
15
  export function debugLog(...args) {
14
- if (isDevPlayer) {
16
+ if (isDebugLoggingEnabled()) {
15
17
  console.log(...args);
16
18
  }
17
19
  }
18
20
  export function debugWarn(...args) {
19
- if (isDevPlayer) {
21
+ if (isDebugLoggingEnabled()) {
20
22
  console.warn(...args);
21
23
  }
22
24
  }
@@ -0,0 +1,55 @@
1
+ "use strict";
2
+
3
+ import { QueryClient } from '@tanstack/react-query';
4
+ function setNetworkMode(queryClient, networkMode) {
5
+ const current = queryClient.getDefaultOptions();
6
+ queryClient.setDefaultOptions({
7
+ ...current,
8
+ queries: {
9
+ ...current.queries,
10
+ staleTime: 60_000,
11
+ retry: 2,
12
+ networkMode
13
+ },
14
+ mutations: {
15
+ ...current.mutations,
16
+ retry: 0,
17
+ networkMode
18
+ }
19
+ });
20
+ }
21
+ export function createHostQueryClient() {
22
+ return new QueryClient({
23
+ defaultOptions: {
24
+ queries: {
25
+ staleTime: 60_000,
26
+ retry: 2
27
+ },
28
+ mutations: {
29
+ retry: 0
30
+ }
31
+ }
32
+ });
33
+ }
34
+ export function createOfflineHostActivationCallbacks(queryClient) {
35
+ return {
36
+ prepareForOfflineActivation: () => queryClient.cancelQueries(),
37
+ completeOfflineActivation: async () => {
38
+ setNetworkMode(queryClient, 'always');
39
+ await queryClient.invalidateQueries();
40
+ },
41
+ rollbackOfflineActivation: async () => {
42
+ setNetworkMode(queryClient, undefined);
43
+ await queryClient.invalidateQueries();
44
+ }
45
+ };
46
+ }
47
+ export function resolveSdkMountDecision(value) {
48
+ return typeof value === 'boolean' ? {
49
+ shouldMountChildren: value,
50
+ contractSupported: true
51
+ } : {
52
+ shouldMountChildren: true,
53
+ contractSupported: false
54
+ };
55
+ }
@@ -0,0 +1,44 @@
1
+ "use strict";
2
+
3
+ import { useCallback, useEffect, useMemo } from 'react';
4
+ import { useQueryClient } from '@tanstack/react-query';
5
+ import { getAppLogger } from '@microsoft/power-apps-native-common';
6
+ import { fetchInstanceUrl } from "../platform/DataverseGlobalDiscoveryApi.js";
7
+ import { hasDataverseDatabaseReferences } from "../platform/DataverseDataSources.js";
8
+ import { HostOperationNames } from "../telemetry/index.js";
9
+ import { createOfflineHostActivationCallbacks, resolveSdkMountDecision } from "./createOfflineHostActivationCallbacks.js";
10
+ import { useOfflineIntegration } from './useOfflineIntegration';
11
+ export function useOfflineIntegrationWithStartupGate({
12
+ powerConfig,
13
+ offlineProfile,
14
+ acquireToken,
15
+ hasRealAccount,
16
+ isAuthReady
17
+ }) {
18
+ const queryClient = useQueryClient();
19
+ const hasDataverse = hasDataverseDatabaseReferences(powerConfig.databaseReferences);
20
+ const resolveInstanceUrl = useCallback(async (environmentId, region, acqToken) => hasDataverse ? (await fetchInstanceUrl(environmentId, region, acqToken)) ?? null : null, [hasDataverse]);
21
+ const hostActivationCallbacks = useMemo(() => createOfflineHostActivationCallbacks(queryClient), [queryClient]);
22
+ const offline = useOfflineIntegration({
23
+ powerConfig,
24
+ offlineProfile,
25
+ acquireToken,
26
+ isSignedIn: hasRealAccount,
27
+ authState: isAuthReady ? hasRealAccount ? 'authenticated' : 'signed-out' : 'hydrating',
28
+ fetchInstanceUrl: resolveInstanceUrl,
29
+ hostActivationCallbacks
30
+ });
31
+ const mountDecision = resolveSdkMountDecision(offline.shouldMountChildren);
32
+ useEffect(() => {
33
+ if (!mountDecision.contractSupported) {
34
+ getAppLogger().trackErrorEvent(HostOperationNames.OfflineRoutingContractUnsupported, {
35
+ fallbackRoute: 'online'
36
+ });
37
+ }
38
+ }, [mountDecision.contractSupported]);
39
+ return {
40
+ isEnabled: offline.isEnabled,
41
+ StatusOverlay: offline.StatusOverlay,
42
+ shouldMountChildren: mountDecision.shouldMountChildren
43
+ };
44
+ }
@@ -0,0 +1,9 @@
1
+ "use strict";
2
+
3
+ export function useOfflineIntegrationWithStartupGate(_options) {
4
+ return {
5
+ isEnabled: false,
6
+ StatusOverlay: null,
7
+ shouldMountChildren: true
8
+ };
9
+ }
@@ -0,0 +1,13 @@
1
+ "use strict";
2
+
3
+ export function hasDataverseDatabaseReferences(databaseReferences) {
4
+ return Object.keys(databaseReferences ?? {}).length > 0;
5
+ }
6
+ export function buildCdsDataSources(databaseReferences, dataverseOrgUrl) {
7
+ return Object.values(databaseReferences).flatMap(databaseReference => Object.values(databaseReference.dataSources ?? {}).map(dataSource => ({
8
+ runtimeUrl: dataverseOrgUrl ? `${dataverseOrgUrl}/api/data/v9.0/` : '',
9
+ entitySetName: dataSource.entitySetName ?? '',
10
+ logicalName: dataSource.logicalName ?? '',
11
+ version: '9.2'
12
+ })));
13
+ }
@@ -0,0 +1,89 @@
1
+ "use strict";
2
+
3
+ import { getHttpCorrelationProperties } from '@microsoft/power-apps-native-common/http';
4
+ import * as SecureStore from 'expo-secure-store';
5
+ import { getAppLogger, HostOperationNames, platformHttpClient } from "../telemetry/index.js";
6
+ import { buildGlobalDiscoveryUrl, globalDiscoveryScope } from "./GlobalDiscovery.js";
7
+ const INSTANCE_URL_CACHE_KEY_PREFIX = 'microsoft.power-apps-native-host.instance-url.v1';
8
+ const INSTANCE_URL_CACHE_TTL_MS = 30 * 24 * 60 * 60 * 1000;
9
+ function getInstanceUrlCacheKey(environmentId, region) {
10
+ return `${INSTANCE_URL_CACHE_KEY_PREFIX}.${region.toLowerCase()}.${environmentId.toLowerCase()}`;
11
+ }
12
+ async function getCachedInstanceUrl(environmentId, region) {
13
+ try {
14
+ const cachedValue = await SecureStore.getItemAsync(getInstanceUrlCacheKey(environmentId, region));
15
+ if (!cachedValue) {
16
+ return null;
17
+ }
18
+ const entry = JSON.parse(cachedValue);
19
+ if (typeof entry.instanceUrl !== 'string' || typeof entry.expiresAt !== 'number' || entry.expiresAt <= Date.now()) {
20
+ return null;
21
+ }
22
+ return entry.instanceUrl;
23
+ } catch {
24
+ return null;
25
+ }
26
+ }
27
+ async function cacheInstanceUrl(environmentId, region, instanceUrl) {
28
+ try {
29
+ const entry = {
30
+ instanceUrl,
31
+ expiresAt: Date.now() + INSTANCE_URL_CACHE_TTL_MS
32
+ };
33
+ await SecureStore.setItemAsync(getInstanceUrlCacheKey(environmentId, region), JSON.stringify(entry));
34
+ } catch {
35
+ // SecureStore may be unavailable (for example, on web); discovery still succeeds.
36
+ }
37
+ }
38
+ function getSafeErrorName(error) {
39
+ return error instanceof Error ? error.constructor.name : typeof error;
40
+ }
41
+
42
+ /** Fetch the Dataverse instance URL from the Dataverse Global Discovery Service. */
43
+ export async function fetchInstanceUrl(environmentId, region, acquireToken, _bearerToken) {
44
+ const scenario = getAppLogger().trackScenario(HostOperationNames.PlatformFetchInstanceUrl, {
45
+ environmentId,
46
+ region
47
+ });
48
+ try {
49
+ const cachedInstanceUrl = await getCachedInstanceUrl(environmentId, region);
50
+ if (cachedInstanceUrl) {
51
+ scenario.complete({
52
+ hasInstanceUrl: true,
53
+ cacheHit: true
54
+ });
55
+ return cachedInstanceUrl;
56
+ }
57
+ const url = buildGlobalDiscoveryUrl(environmentId, region);
58
+ const token = await acquireToken([globalDiscoveryScope(region)]);
59
+ if (!token) {
60
+ throw new Error('fetchInstanceUrl: failed to acquire Dataverse Global Discovery token');
61
+ }
62
+ const response = await platformHttpClient.request(url, {
63
+ method: 'GET',
64
+ headers: {
65
+ Authorization: `Bearer ${token}`,
66
+ 'Content-Type': 'application/json'
67
+ }
68
+ });
69
+ if (!response.ok) {
70
+ throw new Error(`fetchInstanceUrl: Global Discovery returned ${response.status} ${response.statusText}`);
71
+ }
72
+ const result = await response.json();
73
+ const instanceUrl = result.value?.find(instance => instance.EnvironmentId?.toLowerCase() === environmentId.toLowerCase())?.ApiUrl;
74
+ if (instanceUrl) {
75
+ await cacheInstanceUrl(environmentId, region, instanceUrl);
76
+ }
77
+ scenario.complete({
78
+ hasInstanceUrl: instanceUrl !== undefined,
79
+ cacheHit: false,
80
+ ...getHttpCorrelationProperties(response.headers)
81
+ });
82
+ return instanceUrl;
83
+ } catch (error) {
84
+ scenario.fail(undefined, {
85
+ errorName: getSafeErrorName(error)
86
+ });
87
+ throw error;
88
+ }
89
+ }