@microsoft/power-apps-native-host 0.2.25 → 0.3.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +21 -10
- package/compatibility/current.json +31 -4
- package/lib/NOTICE.txt +1325 -24
- package/lib/commonjs/auth/AuthContext.js +110 -33
- package/lib/commonjs/bridge/NativePowerAppsBridge.js +11 -18
- package/lib/commonjs/bridge/PowerPagesBridge.js +27 -8
- package/lib/commonjs/bridge/bridgeState.js +40 -0
- package/lib/commonjs/config/babelConfig.js +13 -0
- package/lib/commonjs/config/expoConfig.js +71 -0
- package/lib/commonjs/config/metroConfig.js +70 -0
- package/lib/commonjs/config/metroLogger.js +182 -0
- package/lib/commonjs/config/tamaguiConfig.js +98 -0
- package/lib/commonjs/connections/ConnectionSetupScreen.js +459 -80
- package/lib/commonjs/connections/PowerPlatformConnectionApi.js +946 -0
- package/lib/commonjs/connections/connectionMetadata.js +127 -0
- package/lib/commonjs/connections/connectionState.js +96 -0
- package/lib/commonjs/connections/disambiguate.js +19 -11
- package/lib/commonjs/connections/resolveConnections.js +348 -0
- package/lib/commonjs/connections/types.js +106 -0
- package/lib/commonjs/{platform → connections}/useConnectionRefs.js +130 -65
- package/lib/commonjs/connections/useConnectionSetup.js +111 -52
- package/lib/commonjs/controls/FilePicker.js +6 -0
- package/lib/commonjs/controls/ImagePicker.js +8 -0
- package/lib/commonjs/index.js +167 -4
- package/lib/commonjs/logger.js +9 -6
- package/lib/commonjs/offline/createOfflineHostActivationCallbacks.js +61 -0
- package/lib/commonjs/offline/useOfflineIntegrationWithStartupGate.js +48 -0
- package/lib/commonjs/offline/useOfflineIntegrationWithStartupGate.web.js +13 -0
- package/lib/commonjs/platform/DataverseDataSources.js +18 -0
- package/lib/commonjs/platform/DataverseGlobalDiscoveryApi.js +94 -0
- package/lib/commonjs/platform/GlobalDiscovery.js +51 -0
- package/lib/commonjs/platform/PowerPlatformEnvironmentApi.js +6 -106
- package/lib/commonjs/provider/PowerAppsHostProvider.js +157 -25
- package/lib/commonjs/provider/PowerAppsProvider.js +178 -134
- package/lib/commonjs/provider/SessionDetailsGesture.js +208 -0
- package/lib/commonjs/telemetry/OfflineSdkTelemetry.js +95 -0
- package/lib/commonjs/telemetry/OfflineSdkTelemetry.web.js +9 -0
- package/lib/commonjs/telemetry/index.js +22 -3
- package/lib/module/auth/AuthContext.js +110 -33
- package/lib/module/bridge/NativePowerAppsBridge.js +12 -19
- package/lib/module/bridge/PowerPagesBridge.js +27 -8
- package/lib/module/bridge/bridgeState.js +34 -0
- package/lib/module/config/babelConfig.js +9 -0
- package/lib/module/config/expoConfig.js +67 -0
- package/lib/module/config/metroConfig.js +65 -0
- package/lib/module/config/metroLogger.js +175 -0
- package/lib/module/config/tamaguiConfig.js +93 -0
- package/lib/module/connections/ConnectionSetupScreen.js +460 -81
- package/lib/module/connections/PowerPlatformConnectionApi.js +932 -0
- package/lib/module/connections/connectionMetadata.js +112 -0
- package/lib/module/connections/connectionState.js +82 -0
- package/lib/module/connections/disambiguate.js +19 -12
- package/lib/module/connections/resolveConnections.js +343 -0
- package/lib/module/connections/types.js +91 -0
- package/lib/module/{platform → connections}/useConnectionRefs.js +131 -66
- package/lib/module/connections/useConnectionSetup.js +113 -54
- package/lib/module/controls/FilePicker.js +6 -0
- package/lib/module/controls/ImagePicker.js +8 -0
- package/lib/module/index.js +14 -4
- package/lib/module/logger.js +8 -6
- package/lib/module/offline/createOfflineHostActivationCallbacks.js +55 -0
- package/lib/module/offline/useOfflineIntegrationWithStartupGate.js +44 -0
- package/lib/module/offline/useOfflineIntegrationWithStartupGate.web.js +9 -0
- package/lib/module/platform/DataverseDataSources.js +13 -0
- package/lib/module/platform/DataverseGlobalDiscoveryApi.js +89 -0
- package/lib/module/platform/GlobalDiscovery.js +45 -0
- package/lib/module/platform/PowerPlatformEnvironmentApi.js +5 -104
- package/lib/module/provider/PowerAppsHostProvider.js +160 -28
- package/lib/module/provider/PowerAppsProvider.js +182 -138
- package/lib/module/provider/SessionDetailsGesture.js +203 -0
- package/lib/module/telemetry/OfflineSdkTelemetry.js +91 -0
- package/lib/module/telemetry/OfflineSdkTelemetry.web.js +5 -0
- package/lib/module/telemetry/index.js +15 -4
- package/lib/typescript/commonjs/auth/AuthContext.d.ts +3 -1
- package/lib/typescript/commonjs/auth/AuthContext.web.d.ts +1 -0
- package/lib/typescript/commonjs/bridge/bridgeState.d.ts +5 -0
- package/lib/typescript/commonjs/config/babelConfig.d.ts +12 -0
- package/lib/typescript/commonjs/config/expoConfig.d.ts +21 -0
- package/lib/typescript/commonjs/config/metroConfig.d.ts +6 -0
- package/lib/typescript/commonjs/config/metroLogger.d.ts +21 -0
- package/lib/typescript/commonjs/config/tamaguiConfig.d.ts +1792 -0
- package/lib/typescript/commonjs/connections/ConnectionSetupScreen.d.ts +28 -7
- package/lib/typescript/commonjs/{platform → connections}/PowerPlatformConnectionApi.d.ts +52 -43
- package/lib/typescript/commonjs/connections/connectionMetadata.d.ts +66 -0
- package/lib/typescript/commonjs/connections/connectionState.d.ts +16 -0
- package/lib/typescript/commonjs/connections/disambiguate.d.ts +4 -2
- package/lib/typescript/commonjs/{platform → connections}/resolveConnections.d.ts +22 -20
- package/lib/typescript/commonjs/connections/types.d.ts +129 -1
- package/lib/typescript/commonjs/{platform → connections}/useConnectionRefs.d.ts +36 -25
- package/lib/typescript/commonjs/connections/useConnectionSetup.d.ts +12 -8
- package/lib/typescript/commonjs/controls/FilePicker.d.ts +7 -1
- package/lib/typescript/commonjs/controls/ImagePicker.d.ts +9 -1
- package/lib/typescript/commonjs/index.d.ts +18 -10
- package/lib/typescript/commonjs/logger.d.ts +4 -4
- package/lib/typescript/commonjs/offline/createOfflineHostActivationCallbacks.d.ts +12 -0
- package/lib/typescript/commonjs/offline/useOfflineIntegrationWithStartupGate.d.ts +15 -0
- package/lib/typescript/commonjs/offline/useOfflineIntegrationWithStartupGate.web.d.ts +6 -0
- package/lib/typescript/commonjs/platform/DataverseDataSources.d.ts +28 -0
- package/lib/typescript/commonjs/platform/DataverseGlobalDiscoveryApi.d.ts +3 -0
- package/lib/typescript/commonjs/platform/GlobalDiscovery.d.ts +4 -0
- package/lib/typescript/commonjs/platform/PowerPlatformEnvironmentApi.d.ts +3 -74
- package/lib/typescript/commonjs/provider/PowerAppsHostProvider.d.ts +29 -18
- package/lib/typescript/commonjs/provider/PowerAppsProvider.d.ts +54 -9
- package/lib/typescript/commonjs/provider/SessionDetailsGesture.d.ts +13 -0
- package/lib/typescript/commonjs/telemetry/OfflineSdkTelemetry.d.ts +15 -0
- package/lib/typescript/commonjs/telemetry/OfflineSdkTelemetry.web.d.ts +2 -0
- package/lib/typescript/commonjs/telemetry/index.d.ts +13 -1
- package/lib/typescript/module/auth/AuthContext.d.ts +3 -1
- package/lib/typescript/module/auth/AuthContext.web.d.ts +1 -0
- package/lib/typescript/module/bridge/bridgeState.d.ts +5 -0
- package/lib/typescript/module/config/babelConfig.d.ts +12 -0
- package/lib/typescript/module/config/expoConfig.d.ts +21 -0
- package/lib/typescript/module/config/metroConfig.d.ts +6 -0
- package/lib/typescript/module/config/metroLogger.d.ts +21 -0
- package/lib/typescript/module/config/tamaguiConfig.d.ts +1792 -0
- package/lib/typescript/module/connections/ConnectionSetupScreen.d.ts +28 -7
- package/lib/typescript/module/{platform → connections}/PowerPlatformConnectionApi.d.ts +52 -43
- package/lib/typescript/module/connections/connectionMetadata.d.ts +66 -0
- package/lib/typescript/module/connections/connectionState.d.ts +16 -0
- package/lib/typescript/module/connections/disambiguate.d.ts +4 -2
- package/lib/typescript/module/{platform → connections}/resolveConnections.d.ts +22 -20
- package/lib/typescript/module/connections/types.d.ts +129 -1
- package/lib/typescript/module/{platform → connections}/useConnectionRefs.d.ts +36 -25
- package/lib/typescript/module/connections/useConnectionSetup.d.ts +12 -8
- package/lib/typescript/module/controls/FilePicker.d.ts +7 -1
- package/lib/typescript/module/controls/ImagePicker.d.ts +9 -1
- package/lib/typescript/module/index.d.ts +18 -10
- package/lib/typescript/module/logger.d.ts +4 -4
- package/lib/typescript/module/offline/createOfflineHostActivationCallbacks.d.ts +12 -0
- package/lib/typescript/module/offline/useOfflineIntegrationWithStartupGate.d.ts +15 -0
- package/lib/typescript/module/offline/useOfflineIntegrationWithStartupGate.web.d.ts +6 -0
- package/lib/typescript/module/platform/DataverseDataSources.d.ts +28 -0
- package/lib/typescript/module/platform/DataverseGlobalDiscoveryApi.d.ts +3 -0
- package/lib/typescript/module/platform/GlobalDiscovery.d.ts +4 -0
- package/lib/typescript/module/platform/PowerPlatformEnvironmentApi.d.ts +3 -74
- package/lib/typescript/module/provider/PowerAppsHostProvider.d.ts +29 -18
- package/lib/typescript/module/provider/PowerAppsProvider.d.ts +54 -9
- package/lib/typescript/module/provider/SessionDetailsGesture.d.ts +13 -0
- package/lib/typescript/module/telemetry/OfflineSdkTelemetry.d.ts +15 -0
- package/lib/typescript/module/telemetry/OfflineSdkTelemetry.web.d.ts +2 -0
- package/lib/typescript/module/telemetry/index.d.ts +13 -1
- package/package.json +52 -13
- package/scripts/build-codegen-package.js +283 -0
- package/scripts/start-web.mjs +9 -5
- package/scripts/upgrade/runtime.js +139 -24
- package/scripts/upgrade-authoring.js +274 -180
- package/scripts/upgrade.js +10 -16
- package/src/config/generated-config.d.ts +4 -0
- package/src/config/index.d.ts +3 -0
- package/src/config/tsconfig.json +26 -0
- package/lib/commonjs/platform/PowerPlatformConnectionApi.js +0 -606
- package/lib/commonjs/platform/resolveConnections.js +0 -264
- package/lib/module/platform/PowerPlatformConnectionApi.js +0 -595
- package/lib/module/platform/resolveConnections.js +0 -259
|
@@ -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,
|
|
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
|
-
|
|
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
|
-
//
|
|
72
|
-
//
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
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,
|
|
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
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
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(
|
|
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,
|
|
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
|
},
|
package/lib/module/index.js
CHANGED
|
@@ -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 {
|
|
18
|
-
export {
|
|
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 "./
|
|
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";
|
package/lib/module/logger.js
CHANGED
|
@@ -3,20 +3,22 @@
|
|
|
3
3
|
/**
|
|
4
4
|
* Conditional debug logger for power-apps-native-host.
|
|
5
5
|
*
|
|
6
|
-
* Debug logs (debugLog / debugWarn) are
|
|
7
|
-
*
|
|
8
|
-
*
|
|
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 (
|
|
16
|
+
if (isDebugLoggingEnabled()) {
|
|
15
17
|
console.log(...args);
|
|
16
18
|
}
|
|
17
19
|
}
|
|
18
20
|
export function debugWarn(...args) {
|
|
19
|
-
if (
|
|
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,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
|
+
}
|